Java多線程的調(diào)度_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
有多個(gè)線程,如何控制它們執(zhí)行的先后次序?
方法一:設(shè)置線程優(yōu)先級。
java.lang.Thread 提供了 setPriority(int newPriority) 方法來設(shè)置線程的優(yōu)先級,但線程的優(yōu)先級是無法保障線程的執(zhí)行次序的,優(yōu)先級只是提高了優(yōu)先級高的線程獲取 CPU 資源的概率。也就是說,這個(gè)方法不靠譜。
方法二:使用線程合并。
使用 java.lang.Thread 的 join() 方法。比如有線程 a,現(xiàn)在當(dāng)前線程想等待 a 執(zhí)行完之后再往下執(zhí)行,那就可以使用 a.join()。一旦線程使用了 a.join(),那么當(dāng)前線程會(huì)一直等待 a 消亡之后才會(huì)繼續(xù)執(zhí)行。什么時(shí)候 a 消亡?a 的 run() 方法執(zhí)行結(jié)束了 a 就消亡了。
這個(gè)方法可以有效地進(jìn)行線程調(diào)度,但卻只能局限于等待一個(gè)線程的執(zhí)行調(diào)度。如果要等待 N 個(gè)線程的話,顯然是無能為力了。而且等待線程必須在被等待線程消亡后才得到繼續(xù)執(zhí)行的指令,無法做到兩個(gè)線程真正意義上的并發(fā),靈活性較差。
方法三:使用線程通信。
java.lang.Object 提供了可以進(jìn)行線程間通信的 wait 與 notify 、notifyAll 等方法。每個(gè) Java 對象都有一個(gè)隱性的線程鎖的概念,通過這個(gè)線程鎖的概念我們讓線程間可以進(jìn)行通信,各線程不再埋頭單干。著名的“生產(chǎn)者-消費(fèi)者”模型就是基于這個(gè)原理實(shí)現(xiàn)的。
這個(gè)方法也可以有效地進(jìn)行線程調(diào)度,而且也不僅僅局限于等待一個(gè)線程的執(zhí)行調(diào)度,具有很大程度上的靈活性。但操作復(fù)雜,不易控制容易造成混亂,程序維護(hù)起來也不太方便。
方法四:使用閉鎖。
閉鎖就像一扇門,在先決條件未達(dá)成之前這扇門是閉著的,線程無法通過,先決條件達(dá)成之后,閉鎖打開,線程就可以繼續(xù)執(zhí)行了。java.util.concurrent.CountDownLatch 是一個(gè)很實(shí)用的閉鎖實(shí)現(xiàn),它提供了 countDown() 和 await() 方法達(dá)成線程執(zhí)行隊(duì)列,這個(gè)方法最適合 M 個(gè)線程等待 N 個(gè)線程執(zhí)行結(jié)束再執(zhí)行的情況。首先初始化一個(gè) CountDownLatch 對象,比如 CountDownLatch doneSignal = new CountDownLatch(N);該對象具有 N 作為計(jì)數(shù)閥值,每個(gè)被等待線程通過對 doneSignal 對象的持有,使用 countDown() 可以將 doneSignal 的計(jì)數(shù)閥值減一;每個(gè)等待線程通過對 doneSignal 對象的持有,使用 await() 阻塞當(dāng)前線程,直到 doneSignal 計(jì)數(shù)閥值減為 0,才繼續(xù)往下執(zhí)行。
這個(gè)方法也可以有效地進(jìn)行線程調(diào)度,而且比方法三更易于管理,開發(fā)者只需控制好 CountDownLatch 即可。但線程執(zhí)行次序管理相對單一,它只是指出當(dāng)前等待線程的數(shù)量,而且 CountDownLatch 的初始閥值一旦設(shè)置就只能遞減下去,無法重置。如需遞減過程中進(jìn)行閥值的重置可以參考 java.util.concurrent.CyclicBarrier。
不管如何,CountDownLatch 對于一定條件下的線程隊(duì)列的達(dá)成還是很有用的。對于復(fù)雜環(huán)境下的線程管理還是卓有成效的。所以熟悉和把握對它的使用還是很有必要的。
以下是一個(gè)實(shí)際項(xiàng)目中 CountDownLatch 的使用的例子:
private Map<Long,DecryptSignalAndPath> afterDecryptFilePathMap = new HashMap<Long,DecryptSignalAndPath>();//TODO 注意容器垃圾數(shù)據(jù)的清理工作 class DecryptRunnable implements Runnable { private ServerFileBean serverFile; private Long fid;//指向解密文件 private CountDownLatch decryptSignal; protected DecryptRunnable(Long fid, ServerFileBean serverFile, CountDownLatch decryptSignal) { this.fid = fid; this.serverFile = serverFile; this.decryptSignal = decryptSignal; } @Override public void run() { //開始解密 String afterDecryptFilePath = null; DecryptSignalAndPath decryptSignalAndPath = new DecryptSignalAndPath(); decryptSignalAndPath.setDecryptSignal(decryptSignal); afterDecryptFilePathMap.put(fid, decryptSignalAndPath); afterDecryptFilePath = decryptFile(serverFile); decryptSignalAndPath.setAfterDecryptFilePath(afterDecryptFilePath); decryptSignal.countDown();//通知所有阻塞的線程 } } class DecryptSignalAndPath { private String afterDecryptFilePath; private CountDownLatch decryptSignal; public String getAfterDecryptFilePath() { return afterDecryptFilePath; } public void setAfterDecryptFilePath(String afterDecryptFilePath) { this.afterDecryptFilePath = afterDecryptFilePath; } public CountDownLatch getDecryptSignal() { return decryptSignal; } public void setDecryptSignal(CountDownLatch decryptSignal) { this.decryptSignal = decryptSignal; } }
需要先執(zhí)行的,被等待線程在這里加入:
CountDownLatch decryptSignal = new CountDownLatch(1); new Thread(new DecryptRunnable(fid, serverFile, decryptSignal)).start();//無需拿到新線程句柄,由 CountDownLatch 自行跟蹤 try { decryptSignal.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block }
需要后執(zhí)行,等待的線程可以這樣加入:
CountDownLatch decryptSignal = afterDecryptFilePathMap.get(fid).getDecryptSignal(); try { decryptSignal.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block }
當(dāng)然,這也僅僅只是一個(gè)簡單的 CountDownLatch 的使用展示,對于 CountDownLatch 來說有點(diǎn)大材小用了,因?yàn)樗梢詣偃胃鼜?fù)雜的多線程環(huán)境。示例中的案例完全可以使用線程通信進(jìn)行搞定。因?yàn)?CountDownLatch 的閥值初始為 1,所以這里甚至完全可以使用方法二所說的線程的合并進(jìn)行取代。
如果讀者覺得以上示例不夠清晰,也可以參考 JDK API 提供的 demo,這個(gè)清晰明了:
class Driver2 { // ... void main() throws InterruptedException { CountDownLatch doneSignal = new CountDownLatch(N); Executor e = ... for (int i = 0; i < N; ++i) // create and start threads e.execute(new WorkerRunnable(doneSignal, i)); doneSignal.await(); // wait for all to finish } } class WorkerRunnable implements Runnable { private final CountDownLatch doneSignal; private final int i; WorkerRunnable(CountDownLatch doneSignal, int i) { this.doneSignal = doneSignal; this.i = i; } public void run() { try { doWork(i); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } }
以上所述是小編給大家介紹的Java多線程的調(diào)度,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
MyBatis 動(dòng)態(tài)SQL和緩存機(jī)制實(shí)例詳解
這篇文章主要介紹了MyBatis 動(dòng)態(tài)SQL和緩存機(jī)制實(shí)例詳解,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-09-09java.net.MalformedURLException異常的解決方法
下面小編就為大家?guī)硪黄猨ava.net.MalformedURLException異常的解決方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05SpringCloud項(xiàng)目的log4j2漏洞解決方案詳解流程
很多小伙伴因?yàn)長og4j2的驚爆0Day漏洞一時(shí)束手無策,這里提供最終解決方案可以進(jìn)行一個(gè)版本號的升級,感興趣的朋友來看看吧2022-04-04SpringBoot常用計(jì)量與bean屬性校驗(yàn)和進(jìn)制數(shù)據(jù)轉(zhuǎn)換規(guī)則全面分析
這篇文章主要介紹了SpringBoot常用計(jì)量、bean屬性校驗(yàn)與進(jìn)制數(shù)據(jù)轉(zhuǎn)換規(guī)則,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10Java多線程并發(fā)編程 Volatile關(guān)鍵字
volatile 關(guān)鍵字是一個(gè)神秘的關(guān)鍵字,也許在 J2EE 上的 JAVA 程序員會(huì)了解多一點(diǎn),但在 Android 上的 JAVA 程序員大多不了解這個(gè)關(guān)鍵字。只要稍了解不當(dāng)就好容易導(dǎo)致一些并發(fā)上的錯(cuò)誤發(fā)生,例如好多人把 volatile 理解成變量的鎖2017-05-05