學習筆記系列開頭慣例發布一些尋親消息
鏈接:https://baobeihuijia.com/bbhj/contents/3/197325.html
-
多線程訪問共享資源沖突
-
臨界區:一段代碼塊存在對共享資源的多線程讀寫操作,稱這段代碼塊為臨界區
-
競態條件:多個線程在臨界區內執行,由于代碼的執行序列不同導致結果無法預測,稱發生了競態條件
-
-
一些線程安全的例子(什么時候加鎖什么時候不加):
-
局部變量是線程安全的(每個線程創建的棧內部獨立擁有)
-
局部變量引用的對象:如果用的對象的公共變量,需要加鎖,因為對象在堆內
-
局部變量引用的對象:如果用的是對象方法內的局部變量**(雖然這種情況下每個線程在堆內單獨擁有對象使用權,但是如果子類重寫方法,新加了線程,那么也會出現兩個線程共享局部變量的情況,所以盡量用final或者private將父類保護起來)**
-
線程安全類:
- string:在定義string類的時候,就已經聲明了final,所以不會出現子類繼承string,并且修改string的讀寫邏輯為多線程,所以對于單獨一個string來說,我們的讀寫是安全的,因為只有有一個線程進行讀寫,但是多個方法的組合之間可以插入不同的別的線程,所以不是原子或者說不是安全的。
- string:在定義string類的時候,就已經聲明了final,所以不會出現子類繼承string,并且修改string的讀寫邏輯為多線程,所以對于單獨一個string來說,我們的讀寫是安全的,因為只有有一個線程進行讀寫,但是多個方法的組合之間可以插入不同的別的線程,所以不是原子或者說不是安全的。
-
-
-
重量級鎖——synchronized【父母考核:我聽父母的,父母來決定我的owner,當追求者很多時,采用這種方案】
-
加鎖room,別的線程會進入block,直到本線程釋放鎖,才會喚醒別的block線程,在這個過程中就算時間片輪完該線程,其他線程也無法喚醒
-
用對象鎖的形式保證了臨界區代碼的原子性,避免多個線程一起執行同一段臨界區代碼,如果這個鎖沒有被用,那么就可以執行這段代碼
-
鎖住對象和鎖住類對象是不同的,并不互斥(也就是說,在執行鎖住對象的時候,當這輪cpu片時間耗盡,cpu也會去執行鎖住類對象的操作,類對象鎖不會被阻塞)
# 方法一 synchronized(對象)# 方法二 class Room {private int counter = 0;// 等價于 synchronized(this)public synchronized void increment() {counter++;}public synchronized void decrement() {counter--;}public synchronized int getCounter() {return counter;} }# 方法三 class Test{// 等價于synchronized(Test.class)public synchronized static void test(){} }
-
Monitor
-
對象頭
- 普通對象:32bit的Mark Word(25位hashcode,每個對象都有自己的哈希碼,4位的GC分代,鎖狀態標志)+32bit的Klass Word(類型指針)
-
數組對象:32bit mark word + 32bit klass word + 32bit的 數組長度
-
-
synchronized的原理:
- 每次線程遇到synchronized(obj),會去檢查obj是否指向操作系統中的一個monitor,如果沒有指向那就將obj對象的mask word 修改為monitor的地址,并將鎖標志位從01改為10,將該線程置為monitor的Owner
- 在此期間如果別的線程執行遇到synchronized(obj),那么就會索引到monitor,發現已經有別的owner,進入entrylist 變為BLOCKED狀態
- 當前owner執行完成后,隨機喚醒一個BLOCKED線程,最后退出該對象時將自己的markword重置為hashcode等
-
重量級鎖的線程自旋優化
- 為了避免線程阻塞,發生上下文切換,從而自旋等待解鎖
- 適合多核cpu,單核的cpu沒有自旋的意義,因為沒有額外的cpu可以來執行自旋(需要等你結束我才能執行,但是我還總要中途打擾你問你好了沒)
- 自旋失敗:幾次自旋重試后還是得不到鎖就進行阻塞
-
-
輕量級鎖(操作系統自動創建,不需要我們顯式定義)【私定終生,我來決定我的owner】
-
加鎖:線程中創建一個LOCK RECORD 00表示輕量級鎖,與對象頭交換mask信息(類似于一種密碼機制,只有當前線程完成后,才會把密碼信息還給對象,別人來訪問交換時這個對象已經是加鎖狀態)
-
鎖重入:當前線程已經拿到鎖了,但是又執行了一遍synchronized(obj),這時會新建一個LOCK RECORD 00棧幀,數據這里會存儲null,說明其他棧幀已經拿到鎖了
-
解鎖:如果是null,直接清除即可,不是null則需要將mask word恢復給對象
-
鎖膨脹(追求者很多產生了競爭,發現決定權給到父母后,就需要與父母溝通)
- 線程加輕量級鎖失敗,進入鎖膨脹流程,變成重量級鎖(修改原始密碼為monitor地址,owner置為當前線程,后續線程進入阻塞)
- 原來線程的輕量級鎖解鎖失敗(因為密碼匹配不對,對象的密碼現在是monitor地址),需要進入重量級鎖的解鎖流程,即找到monitor對象,將owner置為null,并喚醒阻塞線程
-
-
偏向鎖(只有一個線程一直重復用偏向——》有別的線程也來用該對象就會撤銷變為輕量級鎖——》鎖之間有競爭交互就用重量級)
-
原始:將對象的mask word與新建鎖棧幀的頭部信息交換(鎖重入都會嘗試交換信息,造成資源浪費)
-
偏向:將對象的mask word修改為線程ID,鎖重入就不會新建鎖棧幀
-
當前線程id釋放后,線程id還是不會改變,該對象已經從屬于該主線程
-
-
ban掉偏向鎖的方法
- 當一個可偏向的對象調用了hashcode就會ban掉偏向鎖
- 當對象已經偏向一個線程了,另一個線程也來用該對象,但是不競爭,就會變為輕量級(線程id部分變為鎖記錄指針),如果競爭交互就是重量級
- 調用wait,notify也會撤銷,因為wait,notify只有重量級有
-
批量重偏向
- 超過一定閾值數量(20)的對象都在撤銷偏向鎖,就會認為偏向設置的線程有誤,因此會將所有的對象偏向鎖改為別的線程
- 撤銷的性能消耗比較大
- 批量執行
-
批量撤銷
- 撤銷超過閾值(40)的對象都在撤銷偏向鎖換輕量級(說的是所有線程的總數),偏向就要求整個類原始對象和新對象不再偏向,直接輕量級
-
-
JIT即時編譯進行逃逸分析
-
sleep和wait的區別(狀態都是timewaiting)
-
sleep是線程的方法,執行時不會放棄鎖:相當于屋子內的人把門鎖死,直到醒來檢查一下資源是否滿足
-
wait是對象A的方法,會放棄鎖,需要notify叫醒:屋子內的人發現缺資源就出來在門外等著A.wait(),等到資源滿足執行notify【這種方法可以通過資源的識別需要通過while條件來判斷】
-
synchronized(lock){while(資源不滿足){lock.wait();}// 干活 }synchronized(lock){lock.notifyAll() }
-
-