java 並發多線程 鎖的分類概念介紹 多線程下篇(二)


接下來對鎖的概念再次進行深入的介紹
之前反復的提到鎖,通常的理解就是,鎖---互斥---同步---阻塞
其實這是常用的獨占鎖(排它鎖)的概念,也是一種簡單粗暴的解決方案
抗戰電影中,經常出現為了阻止日本人炸橋?炸路?的場景,這只是阻止日本人的一種手段,如果大喊一聲TMD滾蛋,日本人就走了,還炸橋干嘛?
用鎖是為了線程安全,而不是為了上鎖,上鎖是一種途徑,獨占鎖則是“上鎖”的其中一種形式
如果有更優雅的上鎖方式,自然不必要每次都簡單粗暴的使用獨占鎖,不是嘛
 
從幾個維度可以大致分為下面幾種
分類是以鎖為核心,而延展出來的優雅的使用方式
image_5c7f1e9b_3c8d

樂觀鎖與悲觀鎖

  • 樂觀就像小米的宣傳語-永遠相信,美好的事情即將發生;
  • 而悲觀就像透過有色眼睛看世界,永遠都帶有顏色;
對應到程序中
使用鎖是為了什么?因為線程之間會共享數據,共享數據就一定會出現問題嗎?當然是概率的
  • 兩個人去同一個水井打水,如果每次都錯開,那么美好一直存在
  • 如果天天趕到同一個時間點,我相信早晚必有一戰.....
所以如何看待在多線程共享數據中,出現的概率性競爭問題?
  • 樂觀的眼光就是堅信絕大多數時候是沒問題的,只需要最后發生修改或者操作時進行校驗,比如校驗是否被修改過,然后再去進一步處理
  • 悲觀的眼光就是堅信肯定會有問題,所以我就一直加鎖,只要一直鎖住反正肯定不會出現問題
所以你看,樂觀鎖其實可以並沒有鎖,是在邏輯上實現了鎖的業務
假設這樣一種比較極端的場景
A,B兩個線程,共享數據c
A線程每分鍾都會對c進行操作(一天24*60次),B每天都會對c進行一次操作
試想一下樂觀鎖和悲觀鎖之間的性能差異?
在這個場景中可以認為即使沒有任何防范措施,每天正確的概率也會大於99.9%(不考慮一次錯誤后導致的后續連鎖錯誤),如果選用悲觀鎖,將會有多少無謂的損耗?
synchronized就是悲觀鎖,他會為你保障百分百的加鎖與同步

公平鎖與非公平鎖

公平與非公平的字面意思大家都很清晰,沒有人理解起來有難度
比如語文老師對你和同桌的態度一視同仁、比如領導對你和同事的年終你覺得不公平....
但是,對於鎖來說,這個公平與不公平針對的是什么?
之前在監視器概念中提到的,線程如果在某個監視器的等待集合中,那么如果當前線程執行結束后,誰應該被選作下一個進入監視器的線程呢?
這就是鎖的公平性針對的點
  • 對於鎖的請求,每個線程總是有一個先來后到的先后順序,如果按照先后順序,那么就是公平鎖
  • 如果不按照先后順序,隨機的或者按照什么算法優先級等選擇,那么就是非公平鎖
除非有額外的公平性要求,否則不應該使用公平鎖,因為對性能是有損耗的

獨占鎖和共享鎖

  • 如果一個鎖僅僅只能被一個進程擁有,那么他就是獨占的;
  • 如果一個鎖可以同時被多個線程擁有,那么他就是共享的;
獨占鎖會保障任何時候都只是有一個線程進行訪問,ReentrantLock就是獨占鎖,synchronized的原理也是獨占鎖
而讀寫鎖ReadWriteLock就是共享鎖,可以同時允許多個讀線程進行操作

可重入鎖

重入,就好像是你結賬后餐館老板對你說的下次再來!
當一個線程想要獲取一個被其他線程獨占的鎖時,你需要等待,但是如果是自己已經獲得的鎖呢?
答案是你可以多次獲取,這就是可重入
比如一個類中有兩個同步的實例方法,而鎖對象都是當前對象this
如果不可重入會發生什么?
調用了A方法之后,想要調用方法B但是鎖卻被自己占有了,如果不可重入,就成了自己等自己,豈不是傻子?
可重入鎖在內部維護了一個計數器,用於記錄重入次數
自己獲得一次,那么計數器+1,釋放一次計數器-1,如果計數器為0,說明該線程釋放了該鎖,否則,鎖仍舊被該線程持有

自旋鎖

在之前線程簡介中有提到,Java線程是內核級映射的線程(1.2吧?之后)
如果一個線程請求獲取一個鎖時,並不能獲取到,那么將會進入阻塞狀態,也就是會被切換到內核態然后掛起
當該線程獲取到鎖時,又需要切換到內核狀態進行喚醒,說白了需要用戶狀態與內核狀態的切換
而且,這個狀態的切換,還是比較消耗性能的
怎么辦?
有一種解決辦法就是線程繼續運行,過一會兒再次嘗試鎖的獲取
怎么做到的?
其實就是相當於CPU空跑,而不是直接將線程進行掛起
所以說相當於犧牲了CPU的時間片,換取內核狀態的開銷,這就涉及到一個平衡點的問題
如果線程之間競爭不激烈,可能下次的嘗試獲取就成功了
但是如果線程之間競爭非常激烈,下次還是搶不到,下下次還是搶不到......會發生什么情況?
那就是浪費了太多的CPU時間片,而且,你也不能永遠的像“死循環”一樣嘗試呀,所以一般會有一個時長或者次數,總之有個上限
現在對自旋鎖應該有了一定的了解了,自旋就是自己在那邊不停地打轉,不給糖吃就打滾的哭
書面點的說法:
當前線程獲取鎖時,如果發現鎖已經被其他線程占有,並不會馬上阻塞自己,在不放棄CPU的情況下,多次嘗試
剛才也說明了,對於線程是否競爭激烈,自旋鎖有着不同的反應,也說不定會導致CPU白白浪費了時間片,所以要根據業務來

適應性自旋

剛才說到,對於自旋不能無止境的,那就是類似“死循環”,所以都有限制,通常用次數,比如規定次數為5
但是實際情況中,可能有時經常1次就能夠成功,也可能經常5次了還沒有成功,如果1次就成功的還好,如果說限制為5次,每次都5次后還是失敗,這就是純粹的白忙活
所以后來出現了自適應的自旋鎖,自旋的次數(限制)不再是固定的了
  • 如果對於某個鎖,自旋很少成功獲得過,那在以后嘗試獲取這個鎖時將可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源
  • 如果對於某個鎖,自旋等待剛剛成功獲得過鎖,那么將會認為這次也很可能是成功的,所以將會允許自旋,甚至允許更多次數(時間)的自旋等待
對於適應性自旋,大致邏輯是這樣,具體細節由具體算法決定
再次強調
對於自旋,表面上看是減少了阻塞的發生,進而可以減少狀態切換的性能損耗,但是這是以浪費CPU為代價的
所以並不能認為自旋鎖是治療百病的良葯
實際使用中,不能只看到帶來的好處,也需要關注付出的代價,而對於適應性自旋,只是對於自旋的“死板”的一種調優而已

小結

以上分類只是就某一個維度對鎖的概念以及應用的分析
他們概念上不是完全隔離的,不是說就存在那么幾種鎖,A,B,C....A就是A,B就是B
比如人類都是人,但是分為男人、女人,還可以分為好人、壞人等,好人有男女,壞人也有男女
比如獨占鎖屬於悲觀鎖,獨占就是要保障同一時間只有一個線程操作,其他線程必須等待
共享鎖就屬於樂觀鎖,因為他放寬了加鎖的條件
理解鎖的分類有助於后續關於其他高級工具、類的理解與學習


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM