Java 創(chuàng)建線程的 3 種方式
Java 創(chuàng)建線程有多種方式,我們經(jīng)常使用的一般為以下 3 種。
- 直接繼承 Thread 類
- 實(shí)現(xiàn) Runnable 接口
- 實(shí)現(xiàn) Callable 接口
繼承 Thread 類,覆蓋 run 方法
這是創(chuàng)建一個(gè)線程的最簡(jiǎn)單方式,也很清晰,一般在確定創(chuàng)建的線程子類不需要繼承其他的類的情況下使用。
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
*
* /
實(shí)現(xiàn) Runable 接口
實(shí)現(xiàn) Runnable 接口,并將邏輯實(shí)現(xiàn)在 run 方法中,創(chuàng)建該 Runnable 接口實(shí)例后,作為構(gòu)造方法參數(shù)傳入到 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());
}
}
當(dāng)然也可以使用匿名內(nèi)部類來實(shí)現(xiàn) Runnable 接口,在 JDK1.8 中,我們可以使用優(yōu)雅的 Lambda 表達(dá)式.
public class CreateThreadDemo3 {
public static void main(String[] args) {
new Thread(() -> System.out.println("hello,this is " + Thread.currentThread().getName())).start();
}
}
實(shí)現(xiàn) Callable 接口
在 JDK1.5 中 java.util.concurrent 包中增加了 Callable<V>接口,Callable 接口是 Java 對(duì)多線程 API 的增強(qiáng),相較于 Runnable 接口,Callable 接口有 2 點(diǎn)增強(qiáng)
- call 方法有返回值,可以方便的返回線程的執(zhí)行結(jié)果
- call 方法允許聲明異常,如果線程在執(zhí)行過程中發(fā)生異常,會(huì)將異常包裝后在獲取結(jié)果時(shí)捕捉
下面我們使用 Callable 接口演示如何獲取線程的執(zhí)行結(jié)果。
使用 Callable 接口需要 FutureTask 類來進(jìn)行包裝,然后傳入到 Thread 實(shí)例中。步驟一般如下:
- 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call()方法
- 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,并將其做為構(gòu)造參數(shù)傳入到 FutureTask 類實(shí)例中,該 FutureTask 對(duì)象封裝了 Callback 對(duì)象的 call()方法的返回值
- 將 FutureTask 對(duì)象作為 Thread 實(shí)例的構(gòu)造參數(shù)(target)傳入到 Thread 實(shí)例中,創(chuàng)建并啟動(dòng)新線程
- 調(diào)用 FutureTask 對(duì)象的 get()方法來獲得子線程執(zhí)行結(jié)束后的返回值
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接口的實(shí)現(xiàn)實(shí)例作為構(gòu)造參數(shù)傳遞給FutureTask實(shí)例
FutureTask<String> futureTask = new FutureTask<>(new MyCallable1());
//將futureTask作為構(gòu)造參數(shù)傳遞給Thread實(shí)例,并啟動(dòng)這個(gè)線程
new Thread(futureTask).start();
//在主線程中獲取futureTask實(shí)例的執(zhí)行結(jié)果
String result = futureTask.get();
System.out.println(result);//打印線程執(zhí)行結(jié)果--線程的名稱Thread0
}
}
//實(shí)現(xiàn)Callable接口,返回值為String類型
class MyCallable1 implements Callable<String> {
//將線程的名字做為返回值
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
Thread.sleep(1000);
return name;
}
}
/**
* 輸出
* Thread-0
**/
正如我們所設(shè)計(jì)的,主線程開啟子線程后,順利的拿到了子線程的執(zhí)行結(jié)果Thread-0,但是上一個(gè)例子寫法實(shí)在是有些羅嗦,作為一個(gè)邏輯簡(jiǎn)單的 Demo,使用匿名內(nèi)部類和 Lambda 語法來改寫一下,得到一個(gè)更為簡(jiǎn)單的 Demo。
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThreadDemo5 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(() -> Thread.currentThread().getName());
new Thread(task).start();
System.out.println(task.get());
}
}
我們已經(jīng)能夠得到線程的執(zhí)行結(jié)果了,但是如果線程執(zhí)行中發(fā)生異常呢?我們?cè)撊绾尾蹲疆惓#緾allable 接口允許拋出異常,并且在 futureTask 實(shí)例進(jìn)行 get()獲取結(jié)果時(shí),可以將異常捕獲(FutureTask 將 Callable 中發(fā)生的異常進(jìn)行了包裝)。
示例如下:
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<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
try {
String result = futureTask.get();//在get()獲取執(zhí)行結(jié)果時(shí),可以捕捉異常
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {//call方法拋出的異常已經(jīng)被ExecutionException包裝
System.out.println("catch it !!!");
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String> {
@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)
*/
這樣我們就可以捕獲線程執(zhí)行中的異常了。那么 Callable 接口是如何將異常傳遞出去的呢?主線程為什么可以捕獲到 Callable 接口的異常呢?
FutureTask 類實(shí)現(xiàn)了 RunnableFuture 接口,而 RunnableFuture 接口繼承了Runnable和 Future<V>,其中Future<V>接口代表可獲取線程執(zhí)行結(jié)果。
Future 接口中的 get()方法,聲明可以拋出ExecutionException異常,get()方法聲明為:V get() throws InterruptedException, ExecutionException;
在 Callable 接口的 run()方法執(zhí)行時(shí),如果發(fā)生異常,則 FutureTask 類會(huì)捕捉到這個(gè)異常,并且使用ExecutionException進(jìn)行一次包裝。在外部調(diào)用 get()方法時(shí),則會(huì)拋出這個(gè)對(duì)原始異常進(jìn)行包裝之后的ExecutionException。
我們可以截取一段 JDK 的源碼來看得更清晰。
public class FutureTask<V> implements RunnableFuture<V> {
.....
public void run() {
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {//如果callable實(shí)例在運(yùn)行中發(fā)生異常
result = null;
ran = false;
setException(ex);//在這里進(jìn)行異常的包裝
}
if (ran)
set(result);
}
} finally {
....
//callable實(shí)例發(fā)生異常時(shí),調(diào)用setException()方法將異常進(jìn)行記錄
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;//將異常進(jìn)行記錄
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
//report方法為get()方法所調(diào)用,返回線程的執(zhí)行結(jié)果或者異常信息,就是在這個(gè)方法中,對(duì)Callable執(zhí)行的原始異常進(jìn)行了包裝
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {//注意ExecutionException,對(duì)原始異常進(jìn)行了包裝
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);//在這里對(duì)Callable發(fā)送的異常進(jìn)行了包裝
}
//獲取線程執(zhí)行結(jié)果的get()方法
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);//獲取執(zhí)行結(jié)果時(shí),調(diào)用report()方法獲取結(jié)果或者異常信息
}
....
}
小結(jié)
用一張圖來表示 Java 常用的創(chuàng)建線程的三種方式。
