總結java多線程之互斥與同步解決方案

一、線程互斥與同步

互斥:指的是多個線程不能同時訪問共享變量
同步:指的是多個線程按指定的順序執行操作

在同時有多個線程運行過程中,如何達到互斥和同步呢?

  • 加鎖即可

在此使用黑馬筆記中room例子來說明鎖。(ps: 以前就瞭解鎖,但總會記亂,發現使用形象化記憶後就很清楚)

在這裡插入圖片描述

解決互斥

  • 鎖就相當於上圖的房子,裡面放著會被並發訪問的共享變量
  • 此時綠色區域(owner)無線程,此時多個線程想並發訪問房子裡的共享變量,那麼隻允許其中一個線程進入房子訪問,並把房門鎖上。
  • 剩下的沒有拿到鎖的線程隻能在entrylist中排隊
  • owner中的線程訪問結束後會離開房子,並告訴entrylist的線程可以進房子瞭
  • entrylist的線程開始新一輪的掙鎖,如此反復
  • 這樣就能解決互斥的問題

解決同步

(這涉及到為什麼wait(),notify()方法需要用鎖,就是因為隻有用瞭鎖才能完成同步,那麼怎麼完成的呢?)

  • 多個線程同時啟動,如果希望B線程在A線程之後執行
  • 那麼當B先搶到鎖,即先進入瞭房子,此時A隻能在entrylist中排隊
  • 為瞭讓A先執行,那麼可以先讓B進入藍色區域,即waitset中等待,並且把門打開,告訴entrylist中的線程可以進來瞭
  • 那麼A進來後,執行完任務,臨走時通知waitset中的B,B再回到綠色區域執行任務就能保證有序瞭
  • 這樣就能解決同步問題

那麼room這個數據結構其實就是synchronized的核心瞭,接下來總結synchronized原理的時候會一直用room的例子

二、synchronized

很多人對synchronized原理的理解也就停留在知道字節碼有個monitor關鍵字來管理鎖,再淺一點的隻知道怎麼用,再者懂得深一點的卻記不住。我之前就是想深入瞭解一下但覺得苦澀,就看不下去瞭,看瞭黑馬的筆記我覺得這玩意兒其實很簡單,所以好的老師還是比較重要的。那麼在此我也記錄一下怎麼更好的去理解synchronized的底層原理

從字節碼我們可以知道synchronized的底層就是關聯瞭一個monitor,那麼這玩意兒是個什麼東西,怎麼實現鎖的功能呢?

首先,可以把monitor的數據結構簡化成上圖的room,具體點描述如下圖

在這裡插入圖片描述

  • synchronized(鎖對象)的時候,相當於讓鎖對象綁定瞭一個monitor(具體綁定方法不打算在後面總結)
  • 那麼多個線程中方法涉及到該鎖對象時,都會來訪問鎖對象對應的monitor
  • 此時線程thread-2搶到瞭鎖,操作就是讓monitor中的owner字段指向thread-2線程,意味著當前線程獲取到瞭基於該monitor的鎖
  • 其他沒搶到鎖的,monitor會將他們放在Entrylist中等待,這些線程隻能在隊列中等著
  • thread-2線程完成操作後就會退出,並通知entrylist的線程重新搶鎖
  • 如果在執行過程中,線程調用瞭wait()方法,monitor就會將他們放入waitset中等待別人喚醒
  • (看回room結構)owner進入waitset後會把門打開,讓entrylist的線程進來
  • 直到某時刻owner中有線程調用notify()方法,waitset中的線程才會被喚醒,喚醒後會進入entrylist中重新搶鎖

以上就是synchronized的原理。有人就會問瞭,你說的這些文字我都懂啊,搞個圖擺在這也沒啥用。

接下來我將從上圖直接回答下面的常見的問題

wait()和notify()為什麼都得在synchronized後使用?

  • wait()就是將線程放入waitset中,那麼waitset是在room裡面的,不上鎖怎麼能進room中?同理,不進入room,在門外怎麼使用notify()怎麼能叫醒waitset中的線程?

wait()會釋放鎖嗎?

  • 廢話,不開門的話,怎麼放線程進來,就更別提喚醒瞭

notifyALL()為什麼不會喚醒其他鎖對象的線程?

  • 進哪個room才能叫哪個waitset,進瞭Aroom當然隻能叫醒A的waiset瞭

說說synchronized的原理?

  •  把圖畫出來就行瞭

線程什麼時候從runnable變成waiting,什麼時候變成block?

  • 看圖,進入waiset就是wait,所以調wait()就變成waiting狀態進入entrylist就是block,所以被喚醒後以及沒搶到鎖都變block 。。。。。。。。。。。。

註意瞭,這裡涉及monitor的原理都是synchronized最根本的原理,也稱重量級鎖,可以看到monitor會頻繁切換線程狀態,效率比較低。後來synchronized改進瞭,在使用monitor前還有好幾種方案,分別為偏向鎖,輕量鎖,以及自旋優化。這部分也是面試常考點,也容易記亂,但用圖例去記就很清楚。

那麼接下來就說說synchronized的改進

三、輕量鎖與偏向鎖

輕量鎖與偏向鎖的核心都是先不讓線程沖突的時候直接去找monitor,而是先用鎖對象的對象頭字段來解決沖突

(寫博客好累啊。。。算瞭我就總結一些自己覺得關鍵的地方吧)

輕量鎖

  • 對於輕量鎖而言,每個線程維護瞭一個鎖記錄,搶占鎖的過程就是用CAS將自己的信息與鎖對象的對象頭mark word部分交換
  • 這樣其他慢一步的線程CAS會失敗,就意識到鎖已經被占瞭
  • 可重入隻需要在占鎖的時候判斷鎖對象的markword記錄的是不是自己的線程id即可,是的話就能夠獲取鎖,也就是疊加一個鎖記錄
  • 釋放鎖就意味著刪除鎖記錄,直到鎖記錄清空,就將鎖對象頭部被修改的字段變回原樣
  • 輕量鎖是認為不會有競爭,如果發生瞭線程競爭,鎖需要升級,不然上述方法沒有像monitor的entrylist來管理其他競爭暫時沒拿到鎖的線程
  • 鎖升級就是鎖膨脹,直接調monitor來管理,就將owner指向當前線程,然後競爭線程去entrylist排隊
  • 其中涉及自旋優化,就是線程競爭時,第二個線程不用立刻去entrylist中,這樣又要涉及上下文切換,可以自旋一會看鎖能否搶到

偏向鎖

  • 輕量鎖每次占鎖都要用一次CAS來更新鎖對象頭,如果本來就沒啥競爭那CAS就是無用的操作瞭
  • 為瞭解決這個問題,線程搶鎖成功後直接把自己的ID刻在鎖對象頭中,需要判斷重入時隻需判斷ID是否相同即可

到此這篇關於總結java多線程之互斥與同步解決方案的文章就介紹到這瞭,更多相關java多線程之互斥與同步內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: