Java那么多鎖,能鎖住滅霸嗎?
這個圖是不是比上次的好看點?
自旋?
自旋鎖
如果此時拿不到鎖,它不馬上進入阻塞狀態(tài),而愿意等待一段時間。
如果循環(huán)一定的次數還拿不到鎖,那么它才會進入阻塞的狀態(tài),循環(huán)的次數是可以人為指定的。
- 自旋鎖🌰
有一天去全家買咖啡,服務員說真不巧,前面咖啡機壞了,現在正在修,要等10分鐘喔,恰好沒什么急事,那就等吧,坐到一邊休息區(qū)等10分鐘(其它什么事都沒做)。介就是自旋鎖~(自己空轉一會兒)
覺得有點浪費時間?如果你等了15分鐘,還沒修好,那你可能不愿意繼續(xù)等下去了(15分鐘就是設定的自旋等待的最大時間)
上面說自旋鎖循環(huán)的次數是人為指定的,而自適應旋轉鎖,就厲害了,它不需要人為指定循環(huán)次數,它自己本身會判斷要循環(huán)幾次,而且每個線程可能循環(huán)的次數也是不一樣的。
如果這個線程之前拿到過鎖,或者經常拿到一個鎖,那它自己判斷下來再次拿到這個鎖的概率很大,循環(huán)次數就大一些;如果這個線程之前沒拿到過這個鎖,那它就沒把握了,怕消耗CPU,循環(huán)次數就小一點。
它解決的是“鎖競爭時間不確定”的問題,但也不一定它自己設定的一定合適。
- 自適應旋轉鎖🌰
還是前面去全家等咖啡的栗子吧~ 要是等到5分鐘,還沒修好,你目測10分鐘里也修不好,就不再等下去了(循環(huán)次數小);
要是等了10分鐘了,服務員說非常抱歉,快了快了,再1分鐘就可以用了,你也還不急,都已經等了10分鐘了,就多等一會兒嘛(循環(huán)次數大)。
這個是自旋鎖的簡單代碼實現:
- public class SpinLock {
- private AtomicReference<Thread> cas = new AtomicReference<Thread>();
- public void lock() {
- Thread current = Thread.currentThread();
- // 利用CAS
- while (!cas.compareAndSet(null, current)) {
- // DO nothing
- }
- }
- public void unlock() {
- Thread current = Thread.currentThread();
- cas.compareAndSet(current, null);
- }
- }
稍微分析下~
- lock()方法利用CAS,當第一個線程A獲取鎖的時候,能夠成功獲取到,不會進入while循環(huán);
- 如果此時線程A沒有釋放鎖,另一個線程B又來獲取鎖,此時由于不滿足CAS,所以就會進入while循環(huán);
- 然后線程B會不斷判斷是否滿足CAS,直到A線程調用unlock方法釋放了該鎖,它才能獲取鎖。
- 主要存在以下問題:
- 如果某個線程持有鎖的時間過長,就會導致其它等待獲取鎖的線程進入循環(huán)等待,消耗CPU。使用不當會造成CPU使用率極高。
- 本身無法保證公平性,即無法滿足等待時間最長的線程優(yōu)先獲取鎖。不公平的鎖就會存在“線程饑餓”問題。
- 無法保證可重入性。基于自旋鎖,可以實現具備公平性和可重入性質的鎖。
后面這幾個以后有空再詳細來說~
自旋鎖 Vs 阻塞鎖
- 阻塞的栗子~
去一個熱門飯店吃飯,到了門口一看,門口的座位坐滿了人……這咋整……服務員說,您可以先拿個號~小票上掃個二維碼,關注咱們,輪到您了,服務號里就會有提示噠~(很熟悉是不是?)
然后你就先取了號去逛逛周圍小店去了,等輪到你了,手機里收到一條服務提醒消息,到你啦~這時你再去,就可以進店了。
這就是阻塞的過程~
那自旋呢?
就是你自己其它事情都不做,等在那里,就像去超市排隊結賬一樣,你走開的話是沒有人會通知你的,只能重新排隊,需要自己時刻檢查有沒有排到(能不能訪問到共享資源)。
這里插播一下:
阻塞或喚醒一個Java線程需要操作系統(tǒng)切換CPU狀態(tài)來完成,這種狀態(tài)轉換需要耗費處理器時間。
來看看自旋和阻塞的比較~
只升不降的鎖狀態(tài)
鎖主要存在四種狀態(tài):“無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)、重量級鎖狀態(tài)”。
其實這四種狀態(tài)都不是Java語言中的鎖,而是Jvm為了提高鎖的獲取與釋放效率而做的優(yōu)化(使用synchronized時)。
它們會隨著競爭的激烈而逐漸升級,并且是不可逆的升級。
升級過程是這樣的:
- 偏向鎖 -> 輕量級鎖 -> 重量級鎖
關于無鎖~
如果一個方法本來就不涉及共享數據,那它自然就無須任何同步措施去保證正確性,因此會有一些代碼天生就是線程安全的。
它沒有對資源進行鎖定,所有的線程都能訪問并修改同一個資源,但同時只有一個線程能修改成功。
CAS算法 即compare and swap(比較與交換),就是有名的無鎖算法。
狀態(tài)還是詳細比較下吧~
- 知道你想要栗子
你經常去一家店坐在同一個位置吃飯,老板已經記住你啦,每次你去的時候,只要店里客人不多,老板都會給你留著那個座位,這個座位就是你的“偏向鎖”,每次只有你這一個線程用。
有一天你去的時候,店里已經坐滿了,你的位置也被別人坐了,你只能等著(進入競爭狀態(tài)),這時那個座位就升級到“輕量級鎖”了。
要是那個座位特別好(臨窗風景最佳,能隔江賞月~)每次你到的時候,都有其他好幾個人也要去搶那個位置,沒坐到那個位置就不吃飯了>_< 那時那個座位就升級到“重量級鎖”了。
是不是好理解啦?
共享 or 獨享?
獅子們集體喝水😄 小河是共享資源~
要是一只獅子想獨享資源,就這樣了
還是專業(yè)地講下概念~(手機上請點擊圖片放大看看~)
- 還有栗子~
每個禮拜小組的各個成員要共同填一份周報表格,
要是每個人打開的時候,可以加一個寫鎖,即你在寫的時候,別人不能修改,這就是獨享鎖(寫鎖);
但是這份表格大家可以同時打開,看到表格內容(讀取數據),正在改數據的人可以對這份表格加上共享鎖,那這個鎖就是共享鎖。
小總結
對Java的各種鎖概念做了下整理,寫了些自己的理解, 還有很多基礎方面,比如Java的對象頭、對象模型(都比較基礎)、鎖的優(yōu)化、各類鎖代碼實現等,后續(xù)再補充下。有很多公號有很多高水平的文章,需要理解和練習的有太多。
好嘛~ 都講累了,我要先休息休息~