程序員必備技能之多線程的安全機(jī)制
在開始討論java多線程安全機(jī)制之前,首先從內(nèi)存模型來(lái)了解一下什么是多線程的安全性。
一、線程的安全機(jī)制
我們都知道java的內(nèi)存模型中有主內(nèi)存和線程的工作內(nèi)存之分,
主內(nèi)存上存放的是線程共享的變量(實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組的元素),
線程的工作內(nèi)存是線程私有的空間,存放的是線程私有的變量(方法參數(shù)與局部變量)。線程在工作的時(shí)候如果要操作主內(nèi)存上的共享變量,為了獲得更好的執(zhí)行性能并不是直接去修改主內(nèi)存而是會(huì)在線程私有的工作內(nèi)存中創(chuàng)建一份變量的拷貝(緩存),在工作內(nèi)存上對(duì)變量的拷貝修改之后再把修改的值刷回到主內(nèi)存的變量中去,
JVM提供了8中原子操作來(lái)完成這一過(guò)程:lock, unlock, read, load, use, assign, store, write。
深入理解java虛擬機(jī)-jvm最高特性與實(shí)踐這本書中有一個(gè)圖很好的表示了線程,主內(nèi)存和工作內(nèi)存之間的關(guān)系:
如果只有一個(gè)線程當(dāng)然不會(huì)有什么問(wèn)題,但是如果有多個(gè)線程同時(shí)在操作主內(nèi)存中的變量,因?yàn)?種操作的非連續(xù)性和線程搶占cpu執(zhí)行的機(jī)制就會(huì)帶來(lái)沖突的問(wèn)題,也就是多線程的安全問(wèn)題。線程安全的定義就是:如果線程執(zhí)行過(guò)程中不會(huì)產(chǎn)生共享資源的沖突就是線程安全的。
Java里面一般用以下幾種機(jī)制保證線程安全:
二、互斥同步鎖(悲觀鎖)
Synchorized和ReentrantLock就是悲觀鎖。
互斥同步鎖也叫做阻塞同步鎖,特征是會(huì)對(duì)沒(méi)有獲取鎖的線程進(jìn)行阻塞。
要理解互斥同步鎖,首選要明白什么是互斥什么是同步。簡(jiǎn)單的說(shuō)互斥就是非你即我,同步就是順序訪問(wèn)。互斥同步鎖就是以互斥的手段達(dá)到順序訪問(wèn)的目的。操作系統(tǒng)提供了很多互斥機(jī)制比如信號(hào)量,互斥量,臨界區(qū)資源等來(lái)控制在某一個(gè)時(shí)刻只能有一個(gè)或者一組線程訪問(wèn)同一個(gè)資源。
Java里面的互斥同步鎖就是Synchorized和ReentrantLock,前者是由語(yǔ)言級(jí)別實(shí)現(xiàn)的互斥同步鎖,理解和寫法簡(jiǎn)單但是機(jī)制笨拙,在JDK6之后性能優(yōu)化大幅提升,即使在競(jìng)爭(zhēng)激烈的情況下也能保持一個(gè)和ReentrantLock相差不多的性能,所以JDK6之后的程序選擇不應(yīng)該再因?yàn)樾阅軉?wèn)題而放棄synchorized。ReentrantLock是API層面的互斥同步鎖,需要程序自己打開并在finally中關(guān)閉鎖,和synchorized相比更加的靈活,體現(xiàn)在三個(gè)方面:等待可中斷,公平鎖以及綁定多個(gè)條件。但是如果程序猿對(duì)ReentrantLock理解不夠深刻,或者忘記釋放lock,那么不僅不會(huì)提升性能反而會(huì)帶來(lái)額外的問(wèn)題。另外synchorized是JVM實(shí)現(xiàn)的,可以通過(guò)監(jiān)控工具來(lái)監(jiān)控鎖的狀態(tài),遇到異常JVM會(huì)自動(dòng)釋放掉鎖。而ReentrantLock必須由程序主動(dòng)的釋放鎖。
互斥同步鎖都是可重入鎖,好處是可以保證不會(huì)死鎖。但是因?yàn)樯婕暗胶诵膽B(tài)和用戶態(tài)的切換,因此比較消耗性能。JVM開發(fā)團(tuán)隊(duì)在JDK5-JDK6升級(jí)過(guò)程中采用了很多鎖優(yōu)化機(jī)制來(lái)優(yōu)化同步無(wú)競(jìng)爭(zhēng)情況下鎖的性能。比如:自旋鎖和適應(yīng)性自旋鎖,輕量級(jí)鎖,偏向鎖,鎖粗化和鎖消除。
三、非阻塞同步鎖
原子類(CAS)
非阻塞同步鎖也叫樂(lè)觀鎖,相比悲觀鎖來(lái)說(shuō),它會(huì)先進(jìn)行資源在工作內(nèi)存中的更新,然后根據(jù)與主存中舊值的對(duì)比來(lái)確定在此期間是否有其他線程對(duì)共享資源進(jìn)行了更新,如果舊值與期望值相同,就認(rèn)為沒(méi)有更新,可以把新值寫回內(nèi)存,否則就一直重試直到成功。它的實(shí)現(xiàn)方式依賴于處理器的機(jī)器指令:CAS(Compare And Swap)
JUC中提供了幾個(gè)Automic類以及每個(gè)類上的原子操作就是樂(lè)觀鎖機(jī)制
不激烈情況下,性能比synchronized略遜,而激烈的時(shí)候,也能維持常態(tài)。激烈的時(shí)候,Atomic的性能會(huì)優(yōu)于ReentrantLock一倍左右。但是其有一個(gè)缺點(diǎn),就是只能同步一個(gè)值,一段代碼中只能出現(xiàn)一個(gè)Atomic的變量,多于一個(gè)同步無(wú)效。因?yàn)樗荒茉诙鄠€(gè)Atomic之間同步。
非阻塞鎖是不可重入的,否則會(huì)造成死鎖。
四、無(wú)同步方案
1)可重入代碼
在執(zhí)行的任何時(shí)刻都可以中斷-重入執(zhí)行而不會(huì)產(chǎn)生沖突。特點(diǎn)就是不會(huì)依賴堆上的共享資源
2)ThreadLocal/Volaitile
線程本地的變量,每個(gè)線程獲取一份共享變量的拷貝,單獨(dú)進(jìn)行處理。
線程本地存儲(chǔ)
如果一個(gè)共享資源一定要被多線程共享,可以盡量讓一個(gè)線程完成所有的處理操作,比如生產(chǎn)者消費(fèi)者模式中,一般會(huì)讓一個(gè)消費(fèi)者完成對(duì)隊(duì)列上資源的消費(fèi)。典型的應(yīng)用是基于請(qǐng)求-應(yīng)答模式的web服務(wù)器的設(shè)計(jì)
Java 任務(wù)調(diào)度 多線程 開發(fā)者
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。