聊聊Java中是什么方法導(dǎo)致的線程阻塞
為了解決對共享存儲區(qū)的訪問沖突,Java 引入了同步機制,現(xiàn)在讓我們來考察多個線程對共享資源的訪問,顯然同步機制已經(jīng)不夠了,因為在任意時刻所要求的資源不一定已經(jīng)準備好了被訪問,反過來,同一時刻準備好了的資源也可能不止一個。為了解決這種情況下的訪問控制問題,Java 引入了對阻塞機制的支持。
阻塞指的是暫停一個線程的執(zhí)行以等待某個條件發(fā)生(如某資源就緒),學(xué)過操作系統(tǒng)的同學(xué)對它一定已經(jīng)很熟悉了。Java 提供了大量方法來支持阻塞,下面讓我們逐一分析。
二、Java中實現(xiàn)線程阻塞的方法:(1)線程睡眠:Thread.sleep (long millis)方法,使線程轉(zhuǎn)到阻塞狀態(tài)。millis參數(shù)設(shè)定睡眠的時間,以毫秒為單位。當睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()平臺移植性好。
(2)線程等待:Object類中的wait()方法,導(dǎo)致當前的線程等待,直到其他線程調(diào)用此對象的 notify() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價于調(diào)用 wait() 一樣。wait() 和 notify() 方法:兩個方法配套使用,wait() 使得線程進入阻塞狀態(tài),它有兩種形式,一種允許 指定以毫秒為單位的一段時間作為參數(shù),另一種沒有參數(shù),前者當對應(yīng)的 notify() 被調(diào)用或者超出指定時間時線程重新進入可執(zhí)行狀態(tài),后者則必須對應(yīng)的 notify() 被調(diào)用.
(3)線程禮讓,Thread.yield() 方法,暫停當前正在執(zhí)行的線程對象,把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。yield() 使得線程放棄當前分得的 CPU 時間,但是不使線程阻塞,即線程仍處于可執(zhí)行狀態(tài),隨時可能再次分得 CPU 時間。調(diào)用 yield() 的效果等價于調(diào)度程序認為該線程已執(zhí)行了足夠的時間從而轉(zhuǎn)到另一個線程.
(4)線程自閉,join()方法,等待其他線程終止。在當前線程中調(diào)用另一個線程的join()方法,則當前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個進程運行結(jié)束,當前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
(5)suspend() 和 resume() 方法:兩個方法配套使用,suspend()使得線程進入阻塞狀態(tài),并且不會自動恢復(fù),必須其對應(yīng)的resume() 被調(diào)用,才能使得線程重新進入可執(zhí)行狀態(tài)。典型地,suspend() 和 resume() 被用在等待另一個線程產(chǎn)生的結(jié)果的情形:測試發(fā)現(xiàn)結(jié)果還沒有產(chǎn)生后,讓線程阻塞,另一個線程產(chǎn)生了結(jié)果后,調(diào)用 resume() 使其恢復(fù)。Thread中suspend()和resume()兩個方法在JDK1.5中已經(jīng)廢除,不再介紹。因為有死鎖傾向。
這里,筆者放入一張線程生命周期的經(jīng)典圖片,來幫助讀者理解,里面展示了一個線程從創(chuàng)建->運行->阻塞->運行->死亡的全過程:
主線程:JVM調(diào)用程序main()所產(chǎn)生的線程。
當前線程:這個是容易混淆的概念。一般指通過Thread.currentThread()來獲取的進程。
后臺線程:指為其他線程提供服務(wù)的線程,也稱為守護線程。JVM的垃圾回收線程就是一個后臺線程。用戶線程和守護線程的區(qū)別在于,是否等待主線程依賴于主線程結(jié)束而結(jié)束
前臺線程:是指接受后臺線程服務(wù)的線程,其實前臺后臺線程是聯(lián)系在一起,就像傀儡和幕后操縱者一樣的關(guān)系。傀儡是前臺線程、幕后操縱者是后臺線程。由前臺線程創(chuàng)建的線程默認也是前臺線程。可以通過isDaemon()和setDaemon()方法來判斷和設(shè)置一個線程是否為后臺線程。
可見進程:可見進程是指一些不在前臺,但用戶依然可見的進程,舉例來說:各種widget、輸入法等,都屬于visibe。這部分進程雖然不在前臺,但與我們的使用也是密切相關(guān),我們并不希望它被系統(tǒng)終止。
“前臺可見進程服務(wù)于后臺空進程”——這是記錄線程重要性的口訣,
重要性一次遞減即,前臺進程>可見進程>服務(wù)進程>后臺進程>空進程。
線程類的一些常用方法:
sleep(): 強迫一個線程睡眠N毫秒。
isAlive(): 判斷一個線程是否存活。
join(): 等待線程終止。
activeCount(): 程序中活躍的線程數(shù)。
enumerate(): 枚舉程序中的線程。
currentThread(): 得到當前線程。
isDaemon(): 一個線程是否為守護線程。
setDaemon(): 設(shè)置一個線程為守護線程。(用戶線程和守護線程的區(qū)別在于,是否等待主線程依賴于主線程結(jié)束而結(jié)束)
setName(): 為線程設(shè)置一個名稱。
wait(): 強迫一個線程等待。
notify(): 通知一個線程繼續(xù)運行。
setPriority(): 設(shè)置一個線程的優(yōu)先級。
補充:java處理線程阻塞的小技巧
在java中我們使用多線程去處理一些業(yè)務(wù),如果業(yè)務(wù)比較復(fù)雜且當并發(fā)量有挺大的時候,很有可能出現(xiàn)線程阻塞的問題。
案例:有一個觸發(fā)接口,根據(jù)觸發(fā)的信息內(nèi)部開啟多個線程去執(zhí)行業(yè)務(wù),每個線程都會去執(zhí)行兩種業(yè)務(wù):私有業(yè)務(wù)(比如調(diào)用不同的接口)、公共業(yè)務(wù)(比如執(zhí)行存儲、mq發(fā)送等等),當私有業(yè)務(wù)處理時間很快而公共業(yè)務(wù)處理時間比較長,這樣的情景下就可以把私有業(yè)務(wù)和公共業(yè)務(wù)分到不同線程執(zhí)行。
例如:
當觸發(fā)了這個接口,根據(jù)接口觸發(fā)的信息,需要開啟10個線程,那么就可以創(chuàng)建10個線程去執(zhí)行它的私有業(yè)務(wù),然后再額外創(chuàng)建一個線程去拿到前面那10個線程的執(zhí)行返回結(jié)果并進行公共業(yè)務(wù)的處理。
這樣有個好處,就是能讓線程池很快的回收線程,能有效防止線程的阻塞
量化:單個私有業(yè)務(wù)1秒鐘能執(zhí)行完成,單個公共業(yè)務(wù)需要5秒鐘才能執(zhí)行完成,如果接口被觸發(fā),發(fā)現(xiàn)需要創(chuàng)建100個線程執(zhí)行,那么線程池回收這些線程池至少需要等待6秒,如果按照前面說的分成兩個線程,那么就需要創(chuàng)建101個線程,而1秒后就能回收掉執(zhí)行完成的100個線程
但是這里需要做權(quán)衡,如果接口被觸發(fā)的時候發(fā)現(xiàn)需要開啟的線程比較多且公共業(yè)務(wù)很耗時,這種情況下執(zhí)行公共業(yè)務(wù)只有單個線程同步執(zhí)行,那么這個線程就會執(zhí)行比較長的時間,所以執(zhí)行公共業(yè)務(wù)的時候也可根據(jù)實際情況開啟多個線程。
下面寫了個小demo:1.私有業(yè)務(wù)的類:@Componentpublic class Calculation { public Result cal(String req, int a, int b) { System.out.println('請求id:' + req + ' 結(jié)果:' + (a + b)); return new Result(req, a + b); }}2.公共業(yè)務(wù)的類:
@Componentpublic class SomethingElse { public void doElse(Result result) { try { System.out.println(Thread.currentThread().getName() + ' : 開始做其他事情,請求號:' + result.getReq() + ' ,請求結(jié)果:' + result.getSum()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + ' : 完成做其他事情,請求號:' + result.getReq() + ' ,請求結(jié)果:' + result.getSum()); } catch (InterruptedException e) { } }}3.私有業(yè)務(wù)的線程類:
public class CallTask implements Callable<Result> { private String req; private int a; private int b; @Override public Result call() throws Exception { Calculation calculation = Main.applicationContext.getBean(Calculation.class); return calculation.cal(req, a, b); } public CallTask(String req, int a, int b) { this.req = req; this.a = a; this.b = b; } // getter and setter 等等}4.公共業(yè)務(wù)的線程類:
public class ElseTask implements Runnable { private CompletionService<Result> cs; private int threadCount; public ElseTask(CompletionService<Result> cs, int threadCount) { this.cs = cs; this.threadCount = threadCount; } @Override public void run() { SomethingElse somethingElse = Main.applicationContext.getBean(SomethingElse.class); doElse(somethingElse); } private void doElse(SomethingElse somethingElse) { try { for (int i = 0; i < threadCount; i++) {Future<Result> take = cs.take();Result result = take.get();somethingElse.doElse(result); } } catch (Exception e) { } } // getter and setter 等等}6.測試主方法:
@Servicepublic class Main implements ApplicationContextAware { public static ApplicationContext applicationContext = null; public static void main(String[] args) throws InterruptedException { AbstractApplicationContext appContext = new ClassPathXmlApplicationContext('application01.xml'); ExecutorService executorService = Executors.newFixedThreadPool(100); CompletionService<Result> cs = new ExecutorCompletionService(executorService); //這里啟動執(zhí)行計算的線程 cs.submit(new CallTask('req001', 0, 1)); cs.submit(new CallTask('req002', 0, 2)); cs.submit(new CallTask('req003', 0, 3)); cs.submit(new CallTask('req004', 0, 4)); cs.submit(new CallTask('req005', 0, 5)); //專門的監(jiān)控線程,并執(zhí)行其他耗時的線程 executorService.execute(new ElseTask(cs, 5)); executorService.shutdown(); appContext.registerShutdownHook(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
執(zhí)行結(jié)果如下:
核心思想: 將多線程的公有的業(yè)務(wù)抽出來(前提是公有業(yè)務(wù)比較耗時,不然就沒必要了)在其他線程里面執(zhí)行。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持好吧啦網(wǎng)。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章:
