預備知識:Java 執行緒掛起的常用方式有以下幾種
Thread.sleep(long millis)
:這個方法可以讓執行緒掛起一段時間,並釋放 CPU 時間片,等待一段時間後自動恢復執行。這種方式可以用來實現簡單的定時器功能,但如果不恰當使用會影響系統效能。Object.wait()
和Object.notify()
或Object.notifyAll()
:這是一種透過等待某個條件的發生來掛起執行緒的方式。wait()
方法會讓執行緒等待,直到其他執行緒呼叫了notify()
或notifyAll()
方法來通知它。這種方式需要使用 synchronized 或者 ReentrantLock 等同步機制來保證執行緒之間的協作和通訊。LockSupport.park()
和LockSupport.unpark(Thread thread)
:這兩個方法可以讓執行緒掛起和恢復。park()
方法會使當前執行緒掛起,直到其他執行緒呼叫了unpark(Thread thread)
方法來喚醒它。這種方式比較靈活,可以根據需要控制執行緒的掛起和恢復。
先上結論:
1.futureTask.get時透過LockSupport.park()掛起執行緒
2.在Thread.run() 方法中 呼叫 setException(ex)或set(result),然後呼叫LockSupport.unpark(t)喚醒執行緒。
一:示例-引入主題
public class FutureTaskDemo {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("非同步執行緒執行");
Thread.sleep(3000);//模擬執行緒執行任務需要3秒
return "ok";
}
});
Thread t1 = new Thread(futureTask, "執行緒一");
t1.start();
try {
//關鍵程式碼
String s = futureTask.get(2, TimeUnit.SECONDS); //最大等待執行緒2秒
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
二:進入futureTask.get(2, TimeUnit.SECONDS);
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) //重點awaitDone,即完成了最大等待,依然沒有結果就丟擲異常邏輯
throw new TimeoutException();
return report(s);
}
awaitDone返回執行緒任務執行狀態,即小於等於COMPLETING(任務正在執行,等待完成)丟擲異常TimeoutException
三:進入(awaitDone(true, unit.toNanos(timeout)))原理分析
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
3.1 總體解讀awaitDone
利用自旋(for (;?)的方式 ,檢查state(任務狀態)與waitNode(維護等待的執行緒),
第一步:首先檢查if (Thread.interrupted()) 執行緒是否被打斷(LockSupport.parkNanos掛起的執行緒被打斷不丟擲異常),
第二步:判斷任務狀態與waitNode是否入隊+確定最大等待時間
若已完成(if (s > COMPLETING))返回任務狀態
若已完成(if (s == COMPLETING))-->表示正在完成,但尚未完成。則讓出 CPU,進入就緒狀態,等待其他執行緒的執行
若if (q == null)==>建立等待等待節點
若if (!queued)==>表示上一步建立的節點沒有和當前執行緒繫結,故繫結
最後else if (timed)與else,判斷最大等待時間
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
state可能轉換的過程
1.NEW -> COMPLETING -> NORMAL (成功完成)
2.NEW -> COMPLETING -> EXCEPTIONAL (異常)
3.NEW -> CANCELLED (任務被取消)
4.NEW -> INTERRUPTING -> INTERRUPTED(任務被打斷)
3.2 關鍵程式碼
LockSupport.park(this, nanos) ==內部實現==> UNSAFE.park(false, nanos)();
即讓當前執行緒堵塞直至指定的時間(nanos),該方法同Thread.sleep()一樣不會釋放持有的物件鎖,但不同的是Thread.sleep會被打斷(interrupted)並丟擲異常,而LockSupport.park被打斷不會丟擲異常,故在自旋時(for (;?)需判斷if (Thread.interrupted())執行緒是否被打斷(手動丟擲異常)。
四:執行緒執行時state的變化軌跡
4.1:新建時利用構造器設定state=NEW
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // 賦值狀態
}
4.2: 執行緒執行時state可能變化軌跡
public void run() {
..........防止多次執行stat()方法..............
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex); //異常軌跡---> 見下分析
}
if (ran)
set(result); // 正常軌跡--->見下分析
}
} finally {
runner = null;
//----最後結束---防止執行緒被打斷
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
異常軌跡setException(ex)
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
//軌跡變化 2.NEW -> COMPLETING -> EXCEPTIONAL (異常)
}
//否則1: 3.NEW -> CANCELLED (任務被取消)
//否則2: 4.NEW -> INTERRUPTING -> INTERRUPTED(任務被打斷)
}
正常軌跡 set(result);
1.NEW -> COMPLETING -> NORMAL (成功完成)