《深入理解JVM虛擬機(jī)》 - 并發(fā)多線程與線程安全

一、并發(fā)

并發(fā)就是讓計(jì)算機(jī)同時(shí)做幾件事情,比如一個(gè)服務(wù)端同時(shí)為多個(gè)客戶端提供服務(wù)。因?yàn)橛?jì)算機(jī)的運(yùn)算速度和存儲(chǔ)/通信子系統(tǒng)的速度相差太大,大量時(shí)間用在磁盤(pán)IO/網(wǎng)絡(luò)通信/數(shù)據(jù)庫(kù)訪問(wèn)崭捍。通常用1秒內(nèi)服務(wù)端響應(yīng)的請(qǐng)求數(shù)目來(lái)衡量服務(wù)器性能;服務(wù)端是java語(yǔ)言最擅長(zhǎng)的領(lǐng)域之一啰脚,虛擬機(jī)通過(guò)多線程的協(xié)調(diào)保證并發(fā)效率:線程間的協(xié)調(diào)好則效率高殷蛇,協(xié)調(diào)不好比如頻繁阻塞/死鎖則效率低。

Amdahl定律通過(guò)系統(tǒng)中并行化與串行化的比重來(lái)描述多處理器能獲得的運(yùn)算加速能力橄浓;摩爾定律則描述處理器晶體管數(shù)量與運(yùn)行效率直接的關(guān)系粒梦。這兩個(gè)定律是硬件發(fā)展從追求處理器頻率到追求多核心并行處理的發(fā)展過(guò)程。

1. 硬件的并發(fā)與一致性

1)高速緩存:計(jì)算機(jī)的存儲(chǔ)設(shè)備與處理器的運(yùn)算速度有幾個(gè)量級(jí)的差距荸实,這也是為什么有接近處理器運(yùn)算速度的高速緩存匀们,緩存就相當(dāng)于中間件。緩存的問(wèn)題在于數(shù)據(jù)一致性准给,多處理器系統(tǒng)中泄朴,每個(gè)處理器有自己的緩存,對(duì)接同一個(gè)主內(nèi)存露氮,當(dāng)緩存數(shù)據(jù)不一致時(shí)祖灰,以誰(shuí)為準(zhǔn)呢?緩存一致性協(xié)議用于解決該問(wèn)題畔规。

2)亂序執(zhí)行/指令重排:CPU允許將多條指令不按程序順序分開(kāi)發(fā)送給電路單元處理夫植;并非隨意打亂順序,CPU處理指令依賴情況以保證最終結(jié)果的一致性油讯。

二、Java內(nèi)存模型JMM

JMM的設(shè)計(jì)是為了屏蔽硬件平臺(tái)/操作系統(tǒng)的內(nèi)存差異延欠,實(shí)現(xiàn)java程序在各平臺(tái)的一致內(nèi)存訪問(wèn)陌兑;JMM定義了變量的訪問(wèn)規(guī)則,包括變量存儲(chǔ)到內(nèi)存/從內(nèi)存取出變量由捎,變量指實(shí)例/靜態(tài)字段/數(shù)組兔综,不包括棧上的局部變量/方法參數(shù),后者線程私有,不存在競(jìng)爭(zhēng)软驰。

1. 主內(nèi)存與工作內(nèi)存

JMM規(guī)定所有變量存儲(chǔ)在主內(nèi)存(類比主內(nèi)存)涧窒,每個(gè)線程有自己的工作內(nèi)存(類比高速緩存),保存該線程用到的變量的主內(nèi)存拷貝锭亏,線程對(duì)變量的操作在工作內(nèi)存進(jìn)行纠吴。線程間的值傳遞通過(guò)主內(nèi)存進(jìn)行。虛擬機(jī)往往讓工作內(nèi)存優(yōu)先存儲(chǔ)于寄存器/高速緩存慧瘤。

JMM定義的內(nèi)存操作包括:

1)鎖定/解鎖:加鎖把主內(nèi)存變量標(biāo)記為線程獨(dú)占戴已;解鎖后才能被其他線程鎖定

2)讀取/存儲(chǔ):把主內(nèi)存變量的值讀取到工作內(nèi)存;把工作內(nèi)存的變量傳送到主內(nèi)存

3)載入/寫(xiě)入:把讀取操作從主內(nèi)存得到的變量放入工作內(nèi)存的變量副本锅减;把從工作內(nèi)存得到的變量值放入主內(nèi)存變量糖儡。

4)使用/賦值:把工作內(nèi)存變量傳遞給執(zhí)行引擎;把執(zhí)行引擎返回值賦給工作內(nèi)存變量

JMM定義的內(nèi)存操作規(guī)則:1)變量在工作內(nèi)存變化后必須同步回主內(nèi)存怔匣;2)新變量只能在主內(nèi)存中誕生握联;3)一個(gè)變量同一時(shí)刻只能被一個(gè)線程加鎖;對(duì)變量加鎖會(huì)清空工作內(nèi)存中此變量的值每瞒;對(duì)變量解鎖前金闽,必須先同步回主內(nèi)存。

2. 并發(fā)安全特性

1)原子性:一個(gè)或多個(gè)語(yǔ)句要么全部執(zhí)行并且執(zhí)行的過(guò)程不被任何因素打斷独泞,要么都不執(zhí)行呐矾。基本數(shù)據(jù)類型的訪問(wèn)讀寫(xiě)是具有原子性的懦砂;更大范圍的原子性通過(guò)加鎖/解鎖實(shí)現(xiàn)(java synchronised)蜒犯,加鎖使得并發(fā)的過(guò)程看起來(lái)像是串行的,生活中的類比可以想一想火車上的廁所荞膘。

2)可見(jiàn)性:當(dāng)線程修改共享變量的值罚随,其他線程能夠立即得知;JMM規(guī)定變量在工作內(nèi)存修改后必須同步回主內(nèi)存羽资;java的volatile/synchronized/final可以保證可見(jiàn)性:1)java volatile保證立即同步到主內(nèi)存淘菩,并且每次使用前從主內(nèi)存刷新,各個(gè)工作內(nèi)存中volatile變量的值可以不同屠升;2)synchronized保證對(duì)變量解鎖前先同步回主內(nèi)存潮改;3)final修飾的字段在構(gòu)造器中初始化完成后,就對(duì)其他線程可見(jiàn)腹暖。

3)有序性:volatile禁止指令重排汇在;Synchronized:變量同一時(shí)刻只允許一個(gè)線程加鎖。

4)先行發(fā)生原則:如果操作A發(fā)生在操作B之前脏答,那么A產(chǎn)生的影響能夠被B觀察到糕殉,包括共享變量的修改亩鬼,發(fā)送消息等。該原則用于判斷是否線程安全阿蝶,是否會(huì)發(fā)生指令重排雳锋。虛擬機(jī)保證以下次序:1. 程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序執(zhí)行羡洁;2. 管程鎖定規(guī)則:同一個(gè)鎖玷过,解鎖先行于加鎖;3. volatile規(guī)則:對(duì)volatile的寫(xiě)操作先行發(fā)生于讀操作焚廊;4. 線程啟動(dòng)/終止規(guī)則:Thread對(duì)象的start方法>該線程的所有動(dòng)作>線程的終止檢測(cè)冶匹;5. 線程中斷規(guī)則:對(duì)線程interrupt方法的調(diào)用先行發(fā)生于中斷檢測(cè)代碼如interrupted;6. 對(duì)象終結(jié)原則:對(duì)象初始化 > finalize咆瘟;7. 先行發(fā)生原則具有傳遞性:如果A>B; B>C嚼隘,那么A>C

三、Java與線程

線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單位袒餐,是CPU調(diào)度的基本單位飞蛹。Java語(yǔ)言級(jí)別的支持就是Thread類,他的關(guān)鍵方法都聲明為native灸眼。

1.?線程實(shí)現(xiàn)

1)使用內(nèi)核線程實(shí)現(xiàn):內(nèi)核指的是操作系統(tǒng)內(nèi)核卧檐,由OS負(fù)責(zé)線程調(diào)度/切換/把線程交給處理器執(zhí)行;內(nèi)核線程的高級(jí)接口稱為輕量級(jí)進(jìn)程焰宣;2)使用用戶線程實(shí)現(xiàn):用戶線程是和內(nèi)核線程相對(duì)的概念霉囚;沒(méi)有內(nèi)核的支持,實(shí)現(xiàn)復(fù)雜匕积,應(yīng)用少盈罐;3)使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)

2.?Java線程調(diào)度

線程調(diào)度是系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程。分為:

1)協(xié)同式線程調(diào)度:線程執(zhí)行時(shí)間由線程本身控制闪唆,執(zhí)行完成后通知系統(tǒng)切換到其他線程盅粪,不存在線程同步問(wèn)題;缺點(diǎn)是線程執(zhí)行時(shí)間不可控制悄蕾。

2)搶占式線程調(diào)度(java):系統(tǒng)負(fù)責(zé)分配線程執(zhí)行時(shí)間票顾,線程切換不由線程本身決定。如java中yield方法可以讓出執(zhí)行時(shí)間帆调,但無(wú)法獲取執(zhí)行時(shí)間奠骄。通過(guò)設(shè)置線程優(yōu)先級(jí)給某些線程多分配時(shí)間,其他線程少分配一些番刊,java提供10個(gè)優(yōu)先級(jí)級(jí)別含鳞。但是優(yōu)先級(jí)不一定靠譜,還要看操作系統(tǒng)撵枢。

3. 線程狀態(tài)轉(zhuǎn)換

Java語(yǔ)言定義了5中線程狀態(tài):

1)新建new:創(chuàng)建后尚未啟動(dòng)的線程民晒。Thread t = new Thread(new RunnableTask());

2)運(yùn)行Runnable:正在執(zhí)行或者等待CPU分配執(zhí)行時(shí)間;Thread.isAlive()锄禽;

3)等待分為:1. 無(wú)限期等待:不會(huì)被CPU分配時(shí)間潜必,等待其他線程喚醒notify(),沒(méi)有timeout參數(shù)的wait()/join()/park()方法會(huì)導(dǎo)致該狀態(tài)沃但;2. 限期等待:不會(huì)被CPU分配時(shí)間磁滚,一定時(shí)間后系統(tǒng)喚醒,如TimeUnit.MILLISECONDS.sleep(100)宵晚,設(shè)置timeout的上述方法垂攘。

當(dāng)任務(wù)依賴于另一任務(wù)計(jì)算的值,則調(diào)用join()將異步執(zhí)行的線程變成同步淤刃,在線程A中調(diào)用線程B的join方法晒他,那么線程A會(huì)被掛起,直至線程B執(zhí)行結(jié)束(alive)逸贾。

4)阻塞:和等待的區(qū)別在于阻塞在等待獲取排他鎖synchronized陨仅,而等待是在等待時(shí)間

5)結(jié)束:task執(zhí)行完成/run方法正常結(jié)束;interrupt(類似異常退出線程執(zhí)行/類似于break)铝侵;Thread.cancel()灼伤;

四、java對(duì)象線程安全級(jí)別

對(duì)象在一項(xiàng)工作期間咪鲜,不停的中斷和切換狐赡,對(duì)象的屬性可能會(huì)在中斷期間被修改,從而引發(fā)線程安全問(wèn)題疟丙。當(dāng)多個(gè)線程訪問(wèn)一個(gè)對(duì)象颖侄,如果不用考慮這些線程在運(yùn)行時(shí)的調(diào)度/交替,也不需要額外的同步隆敢,調(diào)用行為都可以獲得正確的結(jié)果发皿,那這個(gè)對(duì)象是線程安全的,其代碼本身封裝了正確性保障手段(互斥同步)拂蝎。線程安全根源在于共享數(shù)據(jù)穴墅。

1)不可變Immutable:不可變的對(duì)象(string/final/Number類型)永遠(yuǎn)不會(huì)在多個(gè)線程處于不一致的狀態(tài)。如果共享數(shù)據(jù)是一個(gè)基本數(shù)據(jù)類型温自,可以聲明為final玄货;對(duì)象類型,可以保證對(duì)象行為不會(huì)對(duì)其狀態(tài)產(chǎn)生影響悼泌,比如把對(duì)象中的狀態(tài)變量都用final修飾松捉。

2)絕對(duì)線程安全:基本不存在。

3)相對(duì)線程安全:通常說(shuō)的線程安全都是相對(duì)線程安全馆里;保證對(duì)象的單獨(dú)操作是線程安全的隘世,但對(duì)于連續(xù)操作可柿,則仍然可能需要調(diào)用端用同步手段保證正確性。如Vector/HashTable丙者,他的add/get/size方法都用synchronized修飾复斥;

4)線程兼容:通常說(shuō)的非線程安全,但通過(guò)調(diào)用端的同步手段能保證正確性械媒,如ArrayList和HashMap目锭。

5)線程對(duì)立:基本不存在,無(wú)論調(diào)用端怎么做纷捞,都無(wú)法在多線程中并發(fā)使用的代碼痢虹。

五、線程安全的實(shí)現(xiàn)方法

1.?互斥同步 mutex(mutual exclusion)&synchronization

常用主儡。同步指變量同一時(shí)刻只被一個(gè)線程使用奖唯;互斥是實(shí)現(xiàn)方式(臨界區(qū)/互斥量/信號(hào)量),是一種悲觀并發(fā)策略。

java通過(guò)synchronized實(shí)現(xiàn)互斥同步,編譯后在同步塊前后形成monitorenter/monitorexit字節(jié)碼指令罢艾。字節(jié)碼指令參數(shù)reference表示鎖定對(duì)象。若synchronised明確指定對(duì)象參數(shù)瓢阴,則該對(duì)象作為reference;如果synchronized修飾的是實(shí)例/類方法健无,則相應(yīng)的對(duì)象實(shí)例/class對(duì)象就是鎖對(duì)象荣恐。執(zhí)行monitorenter指令時(shí),嘗試獲取對(duì)象的鎖累贤,如果對(duì)象沒(méi)有被鎖定或者當(dāng)前線程已經(jīng)持有對(duì)象鎖(可重入)叠穆,就把鎖的計(jì)數(shù)器加1;執(zhí)行monitorexit時(shí)鎖計(jì)數(shù)器減1臼膏,計(jì)數(shù)為0時(shí)釋放鎖硼被。獲取對(duì)象鎖失敗,則當(dāng)前線程阻塞渗磅,直到對(duì)象鎖被另一個(gè)線程釋放嚷硫。

互斥同步是重量級(jí)操作,阻塞/喚醒線程需要從用戶態(tài)切換到內(nèi)核態(tài)始鱼。確實(shí)必要才使用synchronized仔掸,虛擬機(jī)優(yōu)化如在通知OS阻塞線程之前加入自旋等待。

2.?非阻塞同步

隨著硬件指令集發(fā)展医清,基于沖突檢測(cè)的樂(lè)觀并發(fā)策略:先操作起暮,沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù)則操作成功;有競(jìng)爭(zhēng)則采取補(bǔ)償措施如重試会烙。

1)testAndSet负懦;2)getAndIncrement:調(diào)用了CAS筒捺,原子類具有該方法,原子類提供的加減操作都是線程安全的纸厉;3)swap焙矛;4)compareAndSwap/CAS:3個(gè)操作數(shù):內(nèi)存位置V/舊的預(yù)期值A(chǔ)/新值B。CAS指令執(zhí)行時(shí)残腌,如果V==A,則V=B贫导,返回V舊值抛猫;否則不更新,返回V舊值孩灯。具有ABA問(wèn)題闺金,如果A改為B后又改回A,則無(wú)法檢測(cè)到修改峰档;5)加載鏈接/條件存儲(chǔ)

3.?無(wú)同步方案

不涉及共享數(shù)據(jù)的方法就不需要同步保證安全败匹。

1)可重入代碼/純代碼:可在代碼執(zhí)行的任何時(shí)刻中斷,轉(zhuǎn)而執(zhí)行另一段代碼讥巡,而在控制權(quán)返回后不會(huì)出現(xiàn)錯(cuò)誤掀亩;用到的狀態(tài)量都由參數(shù)傳入。

2)線程本地存儲(chǔ):如果一段代碼中需要的數(shù)據(jù)必須和其他代碼共享欢顷,那就看這些代碼能否在一個(gè)線程中執(zhí)行槽棍,比如消費(fèi)隊(duì)列的架構(gòu)模式/web交互模型的一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)服務(wù)器線程;線程提供ThreadLocal用于存儲(chǔ)本線程對(duì)象抬驴。

六炼七、鎖優(yōu)化

1)適應(yīng)性自旋:讓等待鎖的線程稍等一下,不放棄處理器的執(zhí)行時(shí)間布持,執(zhí)行一個(gè)忙循環(huán)(自旋)豌拙;鎖占用時(shí)間短的情況下優(yōu)化效果好。自適應(yīng)意味著自旋時(shí)間不固定题暖,而是由上一個(gè)鎖上的自旋時(shí)間和鎖擁有者的狀態(tài)來(lái)決定按傅。

2)鎖消除:對(duì)代碼上要求鎖,但經(jīng)過(guò)逃逸分析判斷數(shù)據(jù)不會(huì)被其他線程訪問(wèn)/競(jìng)爭(zhēng)的鎖進(jìn)行消除芙委,發(fā)生在JIT優(yōu)化逞敷。

3)鎖粗化:編寫(xiě)代碼時(shí),推薦同步塊盡可能小灌侣,從而等待鎖的線程能盡快拿到鎖推捐。對(duì)于反復(fù)加鎖/釋放鎖的操作,會(huì)采用鎖粗化的優(yōu)化方式侧啼。

此外還有輕量級(jí)鎖和偏向鎖牛柒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堪簿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子皮壁,更是在濱河造成了極大的恐慌椭更,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛾魄,死亡現(xiàn)場(chǎng)離奇詭異虑瀑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)滴须,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)舌狗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人扔水,你說(shuō)我怎么就攤上這事痛侍。” “怎么了魔市?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵主届,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我待德,道長(zhǎng)君丁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任将宪,我火速辦了婚禮谈截,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涧偷。我一直安慰自己簸喂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布燎潮。 她就那樣靜靜地躺著喻鳄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪确封。 梳的紋絲不亂的頭發(fā)上除呵,一...
    開(kāi)封第一講書(shū)人閱讀 49,785評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音爪喘,去河邊找鬼颜曾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秉剑,可吹牛的內(nèi)容都是我干的泛豪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诡曙!你這毒婦竟也來(lái)了臀叙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤价卤,失蹤者是張志新(化名)和其女友劉穎劝萤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體慎璧,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡床嫌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胸私。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片既鞠。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖盖文,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚯姆,我是刑警寧澤五续,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站龄恋,受9級(jí)特大地震影響疙驾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜郭毕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一它碎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧显押,春花似錦扳肛、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至兽肤,卻和暖如春套腹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背资铡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工电禀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笤休。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓尖飞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子葫松,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348