java多线程之创建一个线程



        今天来介绍创建线程的几种方式。相信大家学习了java之后很快就能说出两种方式,继承Thread类,或者实现Runnable接口,但是今天除了介绍这两种方式之外还介绍其他两种线程的创建方式。

一、继承Thread类

首先介绍第一种方式,继承Thread类。

public class MyThread extends Thread {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "  这是MyThread线程");
		System.out.println("继承Thread类方式实现一个线程");
	}

	public MyThread(String name) {
		this.setName(name);
	}

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + "  这是main线程");
		MyThread myThread = new MyThread("myThread");
		myThread.start();
	}
}

继承Thread类之后重写Thread类的run方法。并调用其start方法,就会新创建一个线程。这里说一下调用run方法和调用start方法的区别,调用run方法只是简单的掉用一个对象实例方法一样,不会创建线程。调用start方法则会通知java虚拟机,由java虚拟机新建一个线程并执行run方法。

为了证明我们是新启动了一个线程,而不是在main线程里进行。我们使用Thread.currentThread().getName()获取当前执行的线程的名称,并打印在控制台进行比较。

这里如果你想验证start方法和run方法的区别,你可以将myThread.start()更改为myThread.run()观看控制台打印出的线程名称。

二、实现Runnable接口

我们现在介绍第二种方式,实现Runnable接口。

public class MyThread02 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName() + "  这是MyThread02线程");
		System.out.println("继承Thread类方式实现一个线程");
	}

	
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + "  这是main线程");
		MyThread02 myThread02 = new MyThread02();
		new Thread(myThread02,"myThread02").start();
	}
}

同样,我们实现Runnable接口后,重写他的run方法。
但是在创建线程时略有不同,继承Thread类,我们直接new出对应的实例对象,就可以使用他的start方法来启动线程。
但是我们发现Runnable接口内只声明了一个run方法,并没有start方法。所以线程类(Thread)给我们提供了新的构造方法,那就是这两个构造方法:

public Thread(Runnable target, String name) {
        this(null, target, name, 0);
    }
 public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }

我们只需要传一个实现了Runnable接口的对象,就可以获得一个线程,并且该线程的run方法就是该对象重写之后的run方法。同时为了证明我们是新启动了一个线程,我们继续使用Thread.currentThread().getName()来证明

三、实现Callable接口

现在来到我们今天的重点介绍,Callable接口。首先我们实现Callable接口看一下。

public class MyThread03 implements Callable<T>{

}

这时候我们发现,Callable接口有一个泛型。这个泛型代表了什么呢,我们等下讲,我们先进到Callable的源码里看一下。

package java.util.concurrent;


@FunctionalInterface
public interface Callable<V> {
   
    V call() throws Exception;
}

我把多余的注释删除后复制出来贴在这,我们发现他和Runnable接口很相似,也是只有一个方法,但是这个方法叫做call。神奇之处在于,call方法是一个有返回值的方法。我们之前无论是继承Thread类还是实现Runnable接口,我们都知道他的run方法是没有返回值的。这个返回值呢,也是Callable接口的独特之处。有了这个返回值,我们就能让一个线程在运行结束之后,我们可以得到一个线程运行的结果。这个结果也就是Callable接口实现时声明的泛型。

那好,现在我们先随便声明一个返回值,然后我们来使用Callable接口来得到一个线程。

import java.util.concurrent.Callable;

public class MyThread03 implements Callable<Integer>{
	
	public static final Integer i = new Integer(0);

	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName() +":"+ i);
		return i;
	}

	public static void main(String[] args) {
		
	}
}

我们先写到这,然后发现,傻了。为什么呢?因为我们发现Thread类没有提供Callable的构造方法。

java 手工创建子线程 java创建一个线程_System


那怎么办呢?这时候我们就用一个新的类叫FutureTask。我们来观察一下这个FutureTask类发现,他实现了一个叫RunnableFuture的接口,而这个接口继承于Runnable, Future这两个接口,也就是说,他的最上层依旧是一个Runnable接口,这也就是我们常说的设计模式中的适配器模式。而正好FutureTask为我们提供Callable接口的构造方法。

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

好了,回到我们线程创建的代码来。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyThread03 implements Callable<Integer> {

	public static final Integer i = new Integer(0);

	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName() +":"+ i);
		return i;
	}

	public static void main(String[] args) {
		MyThread03 myThread03 = new MyThread03();
		System.out.println(Thread.currentThread().getName()+"我是main线程");
		FutureTask<Integer> futureTask = new FutureTask<Integer>(myThread03);
		new Thread(futureTask).start();
	}
}

我们实例化实现了Callable接口的对象,并使用其实例化FutureTask。使用继承了Runnable接口的FutureTask来实例化线程,并查看控制台打印结果。

这里正好说一下FutureTask的几个方法。我们知道Callable接口和Runnable的接口的方法区别重点在于返回值。那么我们如何来得到这个返回值呢,我们就需要调用FutureTask的get方法。

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

FutureTask,为我们提供了两种get的方式,一种是不带参数的get,就是当一个线程去拿这个返回值时,如果call方法还没有执行完毕,那么该线程陷入阻塞状态等待call方法执行完毕。另一种是带了一个等待时间的参数,也就是说我等你call方法一段时间,如果你在这段时间内执行好了,那么我就继续执行。如果超出这个时间你没执行好,那么我就抛出一个TimeoutException。

接下来我们修改一下代码来体现这两个方法。首先是不带参数的方法

public class MyThread03 implements Callable<Integer> {

	public static final Integer i = new Integer(0);

	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName() +":"+ i);
		Thread.sleep(3000);
		return i;
	}

	public static void main(String[] args) {
		MyThread03 myThread03 = new MyThread03();
		System.out.println(Thread.currentThread().getName()+"我是main线程");
		FutureTask<Integer> futureTask = new FutureTask<Integer>(myThread03);
		new Thread(futureTask).start();
		try {
			System.out.println(futureTask.get());
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"我是main线程");
	}
}

我们让call方法睡眠3秒钟,模拟业务逻辑占用时间。然后在main方法中调用get方法,并在调用get方法后打印信息。执行后发现3秒后,call方法执行完毕后才打印出get方法的返回值和main方法后续信息。也就是说main方法调用了get后,陷入了等待,等待call方法执行完毕后才继续执行。

那么我们再来实验第二个方法,带有超时时间参数的方法。

public class MyThread03 implements Callable<Integer> {

	public static final Integer i = new Integer(0);

	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName() + ":" + i);
		Thread.sleep(3000);
		return i;
	}

	public static void main(String[] args) {
		MyThread03 myThread03 = new MyThread03();
		System.out.println(Thread.currentThread().getName() + "我是main线程");
		FutureTask<Integer> futureTask = new FutureTask<Integer>(myThread03);
		new Thread(futureTask).start();
		try {
			System.out.println(futureTask.get(1, TimeUnit.SECONDS));
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "我是main线程");
//		while (futureTask.isDone()) {
//			try {
//				futureTask.get();
//			} catch (InterruptedException | ExecutionException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//		}

	}
}

运行后发现,控制台没有正确打印出call方法返回值,并打印出了catch到的TimeoutException。说明main线程等待了call方法1秒钟后发现没有执行完毕,于是抛出异常。

Callable接口呢,我们就说到这里。更多Callable的信息大家可以阅读FutureTask的源码来进行了解,并不困难。

四、线程池

接下来介绍第四种线程创建的方式: 线程池。

线程池的方式相对来说比较简单。先贴代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThread04 {
	public static void main(String[] args) {
		Thread t1 = new Thread(()->{
			System.out.println("我是t1线程");
		}, "t1");
		
		ExecutorService executorService = Executors.newCachedThreadPool();
		executorService.submit(t1);
	}
}

我们new 一个线程对象。然后调用线程池的submit方法就可以通过使用线程池的方式来创建一个线程。
这里只是演示如何创建线程,不过多的解释线程池的具体信息。