Java 创建线程的 3 种方式
Java 创建线程有多种方式,我们经常使用的一般为以下 3 种。
- 直接继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口
继承 Thread 类,覆盖 run 方法
这是创建一个线程的最简单方式,也很清晰,一般在确定创建的线程子类不需要继承其他的类的情况下使用。
public class CreateThreadDemo1 {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello,this is " + Thread.currentThread().getName());
}
}
/**
* 输出
*
hello,this is Thread-1
hello,this is Thread-0
hello,this is Thread-2
*
* /
实现 Runable 接口
实现 Runnable 接口,并将逻辑实现在 run 方法中,创建该 Runnable 接口实例后,作为构造方法参数传入到 Thread 中。
public class CreateThreadDemo2 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread th1 = new Thread(myRunnable);
Thread th2 = new Thread(myRunnable);
Thread th3 = new Thread(myRunnable);
th1.start();
th2.start();
th3.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello,this is " + Thread.currentThread().getName());
}
}
当然也可以使用匿名内部类来实现 Runnable 接口,在 JDK1.8 中,我们可以使用优雅的 Lambda 表达式.
public class CreateThreadDemo3 {
public static void main(String[] args) {
new Thread(() -> System.out.println("hello,this is " + Thread.currentThread().getName())).start();
}
}
实现 Callable 接口
在 JDK1.5 中 java.util.concurrent 包中增加了 Callable
接口,Callable 接口是 Java 对多线程 API 的增强,相较于 Runnable 接口,Callable 接口有 2 点增强
- call 方法有返回值,可以方便的返回线程的执行结果
- call 方法允许声明异常,如果线程在执行过程中发生异常,会将异常包装后在获取结果时捕捉
下面我们使用 Callable 接口演示如何获取线程的执行结果。
使用 Callable 接口需要 FutureTask 类来进行包装,然后传入到 Thread 实例中。步骤一般如下:
- 创建 Callable 接口的实现类,并实现 call()方法
- 创建 Callable 实现类的实例,并将其做为构造参数传入到 FutureTask 类实例中,该 FutureTask 对象封装了 Callback 对象的 call()方法的返回值
- 将 FutureTask 对象作为 Thread 实例的构造参数(target)传入到 Thread 实例中,创建并启动新线程
- 调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThreadDemo4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//将Callable接口的实现实例作为构造参数传递给FutureTask实例
FutureTask futureTask = new FutureTask<>(new MyCallable1());
//将futureTask作为构造参数传递给Thread实例,并启动这个线程
new Thread(futureTask).start();
//在主线程中获取futureTask实例的执行结果
String result = futureTask.get();
System.out.println(result);//打印线程执行结果--线程的名称Thread0
}
}
//实现Callable接口,返回值为String类型
class MyCallable1 implements Callable {
//将线程的名字做为返回值
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
Thread.sleep(1000);
return name;
}
}
/**
* 输出
* Thread-0
**/
正如我们所设计的,主线程开启子线程后,顺利的拿到了子线程的执行结果Thread-0
,但是上一个例子写法实在是有些罗嗦,作为一个逻辑简单的 Demo,使用匿名内部类和 Lambda 语法来改写一下,得到一个更为简单的 Demo。
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThreadDemo5 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task = new FutureTask<>(() -> Thread.currentThread().getName());
new Thread(task).start();
System.out.println(task.get());
}
}
我们已经能够得到线程的执行结果了,但是如果线程执行中发生异常呢?我们该如何捕捉异常?Callable 接口允许抛出异常,并且在 futureTask 实例进行 get()获取结果时,可以将异常捕获(FutureTask 将 Callable 中发生的异常进行了包装)。
示例如下:
package xxx....;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThreadDemo6 {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
try {
String result = futureTask.get();//在get()获取执行结果时,可以捕捉异常
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {//call方法抛出的异常已经被ExecutionException包装
System.out.println("catch it !!!");
e.printStackTrace();
}
}
}
class MyCallable implements Callable {
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
Thread.sleep(1000);
Object o = null;
System.out.println(o.toString());//NPE is here
return name;
}
}
/**
* 输出
* catch it !!!
* java.util.concurrent.ExecutionException: java.lang.NullPointerException
* at java.util.concurrent.FutureTask.report(FutureTask.java:122)
* at java.util.concurrent.FutureTask.get(FutureTask.java:192)
* at xxx.....CreateThreadDemo6.main(CreateThreadDemo6.java:14)
* Caused by: java.lang.NullPointerException
* at xxx.....MyCallable.call(CreateThreadDemo6.java:33)
* at xxx.....MyCallable.call(CreateThreadDemo6.java:26)
* at java.util.concurrent.FutureTask.run(FutureTask.java:266)
* at java.lang.Thread.run(Thread.java:748)
*/
这样我们就可以捕获线程执行中的异常了。那么 Callable 接口是如何将异常传递出去的呢?主线程为什么可以捕获到 Callable 接口的异常呢?
FutureTask 类实现了 RunnableFuture 接口,而 RunnableFuture 接口继承了Runnable
和 Future
,其中Future
接口代表可获取线程执行结果。
Future 接口中的 get()方法,声明可以抛出ExecutionException
异常,get()方法声明为:V get() throws InterruptedException, ExecutionException;
在 Callable 接口的 run()方法执行时,如果发生异常,则 FutureTask 类会捕捉到这个异常,并且使用ExecutionException
进行一次包装。在外部调用 get()方法时,则会抛出这个对原始异常进行包装之后的ExecutionException
。
我们可以截取一段 JDK 的源码来看得更清晰。
public class FutureTask implements RunnableFuture {
.....
public void run() {
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {//如果callable实例在运行中发生异常
result = null;
ran = false;
setException(ex);//在这里进行异常的包装
}
if (ran)
set(result);
}
} finally {
....
//callable实例发生异常时,调用setException()方法将异常进行记录
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;//将异常进行记录
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
//report方法为get()方法所调用,返回线程的执行结果或者异常信息,就是在这个方法中,对Callable执行的原始异常进行了包装
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {//注意ExecutionException,对原始异常进行了包装
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);//在这里对Callable发送的异常进行了包装
}
//获取线程执行结果的get()方法
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);//获取执行结果时,调用report()方法获取结果或者异常信息
}
....
}
小结
用一张图来表示 Java 常用的创建线程的三种方式。