詳解FutureTask如何實現(xiàn)最大等待時間
預(yù)備知識
Java 線程掛起的常用方式有以下幾種
Thread.sleep(long millis)
:這個方法可以讓線程掛起一段時間,并釋放 CPU 時間片,等待一段時間后自動恢復(fù)執(zhí)行。這種方式可以用來實現(xiàn)簡單的定時器功能,但如果不恰當(dāng)使用會影響系統(tǒng)性能。
Object.wait()
和 Object.notify()
或 Object.notifyAll()
:這是一種通過等待某個條件的發(fā)生來掛起線程的方式。wait()
方法會讓線程等待,直到其他線程調(diào)用了 notify()
或 notifyAll()
方法來通知它。這種方式需要使用 synchronized 或者 ReentrantLock 等同步機制來保證線程之間的協(xié)作和通信。
LockSupport.park()
和 LockSupport.unpark(Thread thread)
:這兩個方法可以讓線程掛起和恢復(fù)。park()
方法會使當(dāng)前線程掛起,直到其他線程調(diào)用了 unpark(Thread thread)
方法來喚醒它。這種方式比較靈活,可以根據(jù)需要控制線程的掛起和恢復(fù)。
先上結(jié)論
1.futureTask.get時通過LockSupport.park()掛起線程
2.在Thread.run() 方法中 調(diào)用 setException(ex)或set(result),然后調(diào)用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("異步線程執(zhí)行"); Thread.sleep(3000);//模擬線程執(zhí)行任務(wù)需要3秒 return "ok"; } }); Thread t1 = new Thread(futureTask, "線程一"); t1.start(); try { //關(guān)鍵代碼 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,即完成了最大等待,依然沒有結(jié)果就拋出異常邏輯 throw new TimeoutException(); return report(s); }
awaitDone返回線程任務(wù)執(zhí)行狀態(tài),即小于等于COMPLETING(任務(wù)正在運行,等待完成)拋出異常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); } }
總體解讀awaitDone
利用自旋(for (;??)的方式 ,檢查state(任務(wù)狀態(tài))與waitNode(維護等待的線程),
第一步:首先檢查if (Thread.interrupted()) 線程是否被打斷(LockSupport.parkNanos掛起的線程被打斷不拋出異常),
第二步:判斷任務(wù)狀態(tài)與waitNode是否入隊+確定最大等待時間
? 若已完成(if (s > COMPLETING))返回任務(wù)狀態(tài)
? 若已完成(if (s == COMPLETING))-->表示正在完成,但尚未完成。則讓出 CPU,進入就緒狀態(tài),等待其他線程的執(zhí)行
? 若if (q == null)==>創(chuàng)建等待等待節(jié)點
? 若if (!queued)==>表示上一步創(chuàng)建的節(jié)點沒有和當(dāng)前線程綁定,故綁定
? 最后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可能轉(zhuǎn)換的過程 1.NEW -> COMPLETING -> NORMAL (成功完成) 2.NEW -> COMPLETING -> EXCEPTIONAL (異常) 3.NEW -> CANCELLED (任務(wù)被取消) 4.NEW -> INTERRUPTING -> INTERRUPTED(任務(wù)被打斷)
關(guān)鍵代碼
LockSupport.park(this, nanos) ==內(nèi)部實現(xiàn)==> UNSAFE.park(false, nanos)();
? 即讓當(dāng)前線程堵塞直至指定的時間(nanos),該方法同Thread.sleep()一樣不會釋放持有的對象鎖,但不同的是Thread.sleep會被打斷(interrupted)并拋出異常,而LockSupport.park被打斷不會拋出異常,故在自旋時(for (;??)需判斷if (Thread.interrupted())線程是否被打斷(手動拋出異常)。
線程運行時state的變化軌跡
新建時利用構(gòu)造器設(shè)置state=NEW
public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // 賦值狀態(tài) }
線程運行時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; //----最后結(jié)束---防止線程被打斷 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 (任務(wù)被取消) //否則2: 4.NEW -> INTERRUPTING -> INTERRUPTED(任務(wù)被打斷) }
正常軌跡 set(result);
1.NEW -> COMPLETING -> NORMAL (成功完成)
到此這篇關(guān)于詳解FutureTask如何實現(xiàn)最大等待時間的文章就介紹到這了,更多相關(guān)FutureTask最大等待時間內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Web中常用的分頁組件(Java端實現(xiàn))
本文通過使用場景分析給大家介紹了Java Web中常用的分頁組件(Java端實現(xiàn)),非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-05-05Java 中文字符按Unicode排序的實現(xiàn)方法
這篇文章主要介紹了Java 中文字符按Unicode排序的實現(xiàn)方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-10-10Java、Javascript、Javaweb三者的區(qū)別及說明
這篇文章主要介紹了Java、Javascript、Javaweb三者的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02Java中區(qū)別.toString() ,(String),valueOf()方法
這篇文章主要介紹了Java中區(qū)別.toString() ,(String),valueOf()方法,需要的朋友可以參考下2017-01-01