目錄
java支持三種線程創建方式:繼承Thread類,實現Runnable接口,實現Callable接口
1 繼承Thread類
@Test
public void testThread() throws InterruptedException {
new MyThread().start();
TimeUnit.SECONDS.sleep(1);
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("thread start");
}
}
輸出thread start
2 實現Runnable接口
@Test
public void testRunnable() throws InterruptedException {
new Thread(new MyRunnable()).start();
TimeUnit.SECONDS.sleep(1);
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("runnable start");
}
}
輸出runnable start
3 線程啓動原理
通過上述代碼我們知道線程啓動是通過start方法啓動的,那麼咱們看一下start方法
3.1 start 方法
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
start最終會執行start0方法,這是一個本地方法,會把線程設置爲就緒狀態,等待CPU調度。
3.2 run方法
我們都知道線程啓動後最終會調用run方法,我們看一下run方法的實現
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
target在通過實現Runnable創建線程的時候注入給Thread類,所以我們可以知道繼承Thread類和實現Runnable接口本質上沒有太大區別
4 實現Callable接口
4.1 Callable初步使用
@Test
public void testCallable() throws InterruptedException, ExecutionException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + futureTask.get());
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + "callable start");
TimeUnit.SECONDS.sleep(2);
return 1;
}
}
輸出
2020-06-26 23:05:26:callable start
2020-06-26 23:05:28:1
這裏可以看到,兩次輸出是間隔兩秒的,所以get方法是阻塞方法
4.2 Callable方法原理
4.2.1 FutureTask run方法實現
我們知道Thread的構造器是Runnable接口引用的對象,FutureTask實現了又是實現Runnable接口,所以線程啓動的時候調的也是FutureTask的run方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
// 如果state狀態不是線程創建狀態或者當前佔用線程設置失敗直接返回
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 獲取線程call方法結果
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
// 如果call方法已經運行,設置運行後的結果
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
接下來咱們看看set設置執行結果的方法
protected void set(V v) {
// 把當前線程生命週期從新建改爲運行完成
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 把call執行結果賦值給outcame變量
outcome = v;
// 把state設置爲NORMAL狀態
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
分析源碼我們可以知道FutureTask方法實際上就是運行Callable的call方法,把運行結果賦值給成員變量outcome,並且把state值最終設置爲NORMAL
4.2.2 get方法
get方法比較簡單,如果call方法已經運行完成,直接返回outcome值;否則等待運行完成
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
// 如果還沒運行完成則等待運行完成
s = awaitDone(false, 0L);
// 返回結果
return report(s);
}
// 如果是NORMAL狀態直接返回,否者拋異常
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
4.2.3 FutureTask不可重複使用
下面咱們把測試代碼稍微改一下,MyCallable類隨機返回一個整數,然後用兩個線程啓動一個FutureTask類查看返回數據
測試代碼如下
@Test
public void testCallable() throws InterruptedException, ExecutionException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
Integer result = futureTask.get();
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
new Thread(futureTask).start();
result = futureTask.get();
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + "callable start");
TimeUnit.SECONDS.sleep(2);
return new Random().nextInt(1000);
}
}
輸出
2020-06-26 23:24:30:callable start
2020-06-26 23:24:32:711
2020-06-26 23:24:32:711
只輸出了一次callable start,說明call方法只運行了一次。get直接輸出了711,並且沒有阻塞,可以說明第二次get的結果讀取的是第一個執行線程運行的結果
原因也很簡單,我們再看看run方法代碼,其中任意一個線程執行結束後會把state設置爲NORMAL,下一個線程運行的時候判斷不是NEW狀態直接return掉了。
所以我們如果想用兩個線程都要執行Callable方法一定不能偷懶,要用兩個FutureTask實例來運行線程,正確姿勢如下
@Test
public void testCallable() throws InterruptedException, ExecutionException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task1 = new FutureTask<>(myCallable);
FutureTask<Integer> task2 = new FutureTask<>(myCallable);
// 這裏同時啓動兩個線程,提高執行效率
new Thread(task1).start();
new Thread(task2).start();
Integer result = task1.get();
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
result = task2.get();
System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
}
輸出
2020-06-26 23:33:20:callable start
2020-06-26 23:33:20:callable start
2020-06-26 23:33:22:889
2020-06-26 23:33:22:748