深入理解Java虛擬機讀書筆記(四)

四、高效并發(fā)

1. Java內(nèi)存模型與線程

1.1 概述

計算機大部分時間都花磁盤I/O,網(wǎng)絡通訊,數(shù)據(jù)庫訪問,CPU大部分時間都在等待其他資源的狀態(tài),因此需要同時處理多個任務

另一個并發(fā)應用場景,就是服務端同時對多個客戶端提供服務

1.2 硬件效率與一致性

CPU運算速度比訪問內(nèi)存速度快得多,因此加上了高速緩存,將數(shù)據(jù)復制到緩存中,CPU從緩存中讀取數(shù)據(jù)高速運算,運算結束后把結果從緩存同步到主內(nèi)存。這樣可以解決處理器和內(nèi)存速度矛盾,但是會引入緩存一致性問題。多處理器訪問同一主內(nèi)存區(qū)域,各自緩存可能不一致。因此各處理器訪問內(nèi)存需要遵循協(xié)議:MSI MESI MOSI Synapse Firefly Dragon Protocol等

處理器對代碼可能進行亂序執(zhí)行優(yōu)化。相應的,虛擬機即時編譯器也有類似的指令重排序優(yōu)化

1.3 Java內(nèi)存模型

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

所有共享的變量在主內(nèi)存中,每個線程有自己的工作內(nèi)存,線程對變量的操作都必須在工作內(nèi)存中進行,而不能直接操作主內(nèi)存,線程間變量傳遞需要主內(nèi)存

內(nèi)存間交互操作

八個指令:

  • lock:作用于主內(nèi)存,把主內(nèi)存變量標識為線程獨占

  • unlock:作用于主內(nèi)存,把主內(nèi)存變量解鎖,解鎖后其他線程才能鎖定

  • read:作用于主內(nèi)存,把變量的值從主內(nèi)存?zhèn)鞯焦ぷ鲀?nèi)存

  • load:作用于工作內(nèi)存,把主內(nèi)存得到的值,放到工作內(nèi)存變量副本

  • use:作用于工作內(nèi)存,虛擬機遇到需要使用變量的字節(jié)碼指令時會執(zhí)行。把變量的值傳給執(zhí)行引擎

  • assign:作用于工作內(nèi)存,虛擬機遇到給變量賦值的字節(jié)碼指令時會執(zhí)行。把從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量

  • store:作用于工作內(nèi)存,把工作內(nèi)存中的變量的值傳到主內(nèi)存

  • write:作用于主內(nèi)存,把從工作變量中得到的值放到主內(nèi)存變量中

注意:

  • read、load與store、write必須按先后順序執(zhí)行,但是中間可以穿插其他操作

  • read、load與store、write必須成對出現(xiàn),即不允許從一邊讀了但另一邊不接受

  • assign了就一定要同步回主內(nèi)存

  • 沒有assign過不允許同步回主內(nèi)存

  • 不允許工作內(nèi)存中直接使用未初始化(assign/load)的變量,use store操作前,必須執(zhí)行過了assign和load

  • lock可以執(zhí)行多次,但需要解鎖同樣次數(shù)

  • 執(zhí)行l(wèi)ock前,會清空工作內(nèi)存中此變量的值。執(zhí)行引擎使用此變量前,需要先assign或load重新初始化

  • 沒有l(wèi)ock不允許unlock

  • unlock前必須把變量同步回主內(nèi)存,即執(zhí)行store、write

對于volatile型變量規(guī)則

volatile可以保證變量對所有線程可見,但并不是絕對線程安全,多寫場景下仍然有并發(fā)問題,因為寫的操作不是原子的。volatile適合一寫多讀場景

volatile另一個語義是禁止指令重排序優(yōu)化

volatile的讀效率與正常變量差不多,寫效率慢一寫,因為需要插入內(nèi)存屏障

對于long和double型變量規(guī)則

雖然虛擬機規(guī)范中允許把64位數(shù)據(jù)分為兩次32位操作,但具體實現(xiàn)時,仍然會把64位數(shù)據(jù)作為原子操作

原子性、可見性、有序性

原子性:6個基本操作是原子性的,如果不能滿足需要,可以用lock、unlock指令,對應字節(jié)碼指令monitorenter、monitorexit

可見性:一個線程修改了變量的值,其他線程能夠立即知道修改的值。volatile、final、synchronized三個關鍵字都能夠保證變量可見性。被final修飾的字段在構造器中一旦被初始化完成,并且構造器沒有把this的引用傳遞出去,那么其他線程就能看見final字段的值。synchronized的可見性指的是,unlock操作前,必須把變量從工作內(nèi)存同步到主內(nèi)存

有序性:volatile可以禁止指令重排序,synchronized是可以保證同一時刻只有一個線程訪問

先行發(fā)生原則

天然有先后順序,無需進行同步控制

1.4 Java與線程

一對一,映射到輕量級進程

調(diào)度方法:搶占式調(diào)度

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

2. 線程安全與鎖優(yōu)化

2.1 線程安全

多個線程訪問一個對象,不用考慮線程調(diào)度和交替執(zhí)行,不用額外同步,調(diào)用這個對象都可以獲取到正確的結果

Java中的線程安全

  • 不可變:如final修飾,比如String、Number的部分子類(Integer、Long等),注意,AtomicInteger等不屬于

  • 絕對線程安全:沒有

  • 相對線程安全:線程安全的容器,比如Vector、CurrentHashMap,Collections中的SynchronizedCollection()方法包裝的集合

  • 線程兼容:調(diào)用端進行同步,可以保證多線程安全使用,比如HashMap

  • 線程對立:很少,已廢棄

實現(xiàn)方法

  • 互斥同步:即加鎖,synchronized、Lock

  • 非阻塞同步:無鎖,CAS

  • 無同步方案:線程本地存儲,如ThreadLocal

2.2 鎖優(yōu)化

自旋鎖與自適應自旋

忙循環(huán),CPU不讓出執(zhí)行權

自旋一定次數(shù)還沒有獲取到鎖,可以省略自旋

鎖消除

基于逃逸分析,如果不會被其他線程訪問到,可以消除同步措施

鎖粗化

循環(huán)加鎖擴展到外部只加一次鎖

輕量級鎖

對象頭包含兩部分信息:1.對象自身運行時數(shù)據(jù),比如哈希碼,分代年齡,64位OS中長度是64bit;2.方法區(qū)類型數(shù)據(jù)指針,如果是數(shù)組,則還有數(shù)組長度

[圖片上傳失敗...(image-79696f-1651548783059)]

線程中的LockRecord空間,可以存儲對象markword的拷貝,即Displaced Mark Word。加鎖的時候,CAS地更新對象markword為指向LockRecord的指針,如果更新成功,則標志位置為了00,加鎖成功,沒有則加鎖失敗。解鎖過程就是把指針更新回Displaced Mark Word,如果更新成功則解鎖成功,失敗則說明有其他線程嘗試獲取過鎖,要釋放鎖的時候,喚醒被掛起的線程。如果有2個以上線程競爭,則升級為重量級鎖,標志位為10.

偏向鎖

對一個無鎖對象,把線程id記錄到markword中,再有一個線程來獲取鎖,標志位恢復01(無鎖)或00(輕量級鎖)

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

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

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