继承Thread
static class A extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
A a = new A();
a.start();
}
重写Runnable接口
static class B implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
B b = new B();
Thread thread = new Thread(b);
thread.start();
}
继承Thread和重写Runnable的区别:Thread类才是java对线程的唯一抽象,Runnable只是对业务的一种逻辑抽象,Thread可以接受任意一个Runnable的实例并执行。
注:Thread也是实现了Runnable接口的run方法。
Future
继承Thread或者重写Runnable都是不带返回值的创建方式,Future可以获取线程的返回值。
- Future常用的实现类
FutureTask
实例化时接收一个抽象任务Callable
。 - 将该
Future
的实例放入线程Thread
中执行。 - 通过Future的
get()
方法获取线程的返回值。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "success!";
}
});
//FutureTask间接实现了Runnable接口的run
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
新启线程有几种方式
这个问题的答案其实众说纷纭,有2种,3种,4种等等答案,建议比较好的回答是:
按照Java源码中Thread上的注释:
官方说法是在Java中有两种方式创建一个线程用以执行,一种是派生自Thread类,另一种是实现Runnable接口。
当然本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程对象,调用Thread#start启动线程。
至于基于callable接口的方式,因为最终是要把实现了callable接口的对象通过FutureTask包装成Runnable,再交给Thread去执行,所以这个其实可以和实现Runnable接口看成同一类。
而线程池的方式,本质上是池化技术,是资源的复用,和新启线程没什么关系。所以,比较赞同官方的说法,有两种方式创建一个线程用以执行。
run和start的区别
Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。
从Thread的源码可以看到,Thread的start方法中调用了start0()方法,而start0()是个native方法,这就说明Thread#start一定和操作系统是密切相关的。
start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用,如果重复调用会抛出异常(注意,此处可能有面试题:多次调用一个线程的start方法会怎么样?)。
而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。