多線程的風(fēng)險

這篇文章是對《Java并發(fā)編程實戰(zhàn)》1.3節(jié)中線程的風(fēng)險的思考。

安全性問題

使用多線程帶來的最糟糕的問題就是安全性問題。書中以一個線程不安全的UnsafeSequence類為例:

publicclassUnsafeSequence{privateintvalue;/**? ? * 返回一個唯一的數(shù)值? ? *@return*/publicintgetNext(){returnvalue++;? ? }publicintgetValue(){returnvalue;? ? }}復(fù)制代碼

github.com/linnanc/con…

這個類在單線程的情況下能正常工作,但是在多線程的情況下就有可能不正常。需要注意的是,線程的安全性問題描述的是在多線程情況下有可能會帶來安全性問題,并非一定就有安全性問題。比如,下面這段代碼使用兩個線程調(diào)用UnsafeSequence的getNext()方法。

publicclassNextValueThreadextendsThread{privateUnsafeSequence unsafeSequence;publicNextValueThread(UnsafeSequence unsafeSequence){this.unsafeSequence = unsafeSequence;? ? }@Overridepublicvoidrun(){? ? ? ? unsafeSequence.getNext();? ? }publicstaticvoidmain(String[] args){? ? ? ? UnsafeSequence unsafeSequence =newUnsafeSequence();// 使用2個線程NextValueThread[] nextValueThread =newNextValueThread[2];for(inti =0; i < nextValueThread.length; i++) {? ? ? ? ? ? nextValueThread[i] =newNextValueThread(unsafeSequence);? ? ? ? ? ? nextValueThread[i].start();? ? ? ? }for(inti =0; i < nextValueThread.length; i++) {try{? ? ? ? ? ? ? ? nextValueThread[i].join();? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }? ? ? ? }? ? ? ? System.out.println(unsafeSequence.getValue());? ? }}復(fù)制代碼

github.com/linnanc/con…

上面的代碼盡管使用了這個不安全的類,但是它在大部分測試得到的結(jié)果都是正常的。

活躍性問題

多線程會帶來的另外一個問題就是活躍性問題。活躍性問題,包括死鎖,饑餓,活鎖。

死鎖

死鎖的一個著名例子就是哲學(xué)家就餐問題,有5個哲學(xué)家吃中餐,總共只有5根筷子,筷子按下圖的順序擺放。

每個哲學(xué)家就餐時都先拿左邊筷子再拿右邊筷子,如果沒有拿到右邊筷子,左邊的筷子也不會放下,當(dāng)每個人左手都拿到筷子時,因為右手筷子都被另外一個人拿到了,結(jié)果就是一直拿不到一雙筷子,大家都被餓死。要形成死鎖有四個必要的條件:互斥,請求與保持,循環(huán)等待,不可剝奪,以哲學(xué)家用餐的例子來說:

互斥:資源不能被共享,只能被一個線程持有(一根筷子只能被一個哲學(xué)家持有)

請求與保持:已經(jīng)拿到資源的線程會再次請求新的資源,并不會釋放已經(jīng)拿到的資源(左手拿到筷子的哲學(xué)家右手也會去拿筷子,并且不會放下左手已經(jīng)拿到的筷子)

不可剝奪:已經(jīng)被分配的資源,不能從擁有資源的線程中強(qiáng)制剝奪(筷子在哲學(xué)家手里后,其它哲學(xué)家不能搶)

循環(huán)等待:所有線程等待資源形成閉環(huán)(五個哲學(xué)家每個人右邊的筷子都被下一個人的左手拿著,形成閉環(huán))

哲學(xué)家就餐demo:github.com/linnanc/con…

活鎖

如果哲學(xué)家在拿到筷子后的一段時間內(nèi)選擇放下左手已經(jīng)拿住的筷子,也就破壞了請求與保持的條件,這樣死鎖就不會發(fā)生了。但是仍然可能發(fā)生活鎖的現(xiàn)象。比如每個哲學(xué)家拿到左邊筷子后等一段時間沒有等到右邊的筷子,然后他們都放下左邊的筷子,一段時間后,他們又都拿起左邊的筷子,繼續(xù)等右邊的筷子。這樣造成一直循環(huán)重復(fù)的問題就是活鎖。

饑餓

由于線程調(diào)度時,會優(yōu)先調(diào)度高優(yōu)先級的線程,有些低優(yōu)先級的線程可能永遠(yuǎn)都得不到執(zhí)行,這就是饑餓。例如5個哲學(xué)家中某個哲學(xué)家拿筷子的動作遠(yuǎn)沒有其它的哲學(xué)家快,這就可能永遠(yuǎn)拿不到筷子,造成饑餓。

性能問題

在多線程程序中,當(dāng)線程調(diào)度器臨時掛起活躍的線程并轉(zhuǎn)而運行另一個線程就會進(jìn)行上下文切換,上下文切換因為操作系統(tǒng)要在用戶態(tài)和內(nèi)核態(tài)進(jìn)行切換,會帶來很大的開銷:保存和恢復(fù)執(zhí)行上下文,丟失局部性。如果只是在多線程中執(zhí)行很簡單的任務(wù),那很有可能CPU時間更多的花在線程調(diào)度上,而不是線程運行上,這樣更得不償失。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容