【JavaSE】多線程(3)_線程的同步與死鎖及monitor機制

本篇總結線程的同步與死鎖,它的核心問題在於:每一個線程對象輪番搶佔資源 ~
全篇以黃牛賣周杰倫演唱會門票爲例 ~

同步

1 同步問題引出
  • 多個線程同時賣票,造成票數最後出現負數,這種問題稱爲:不同步操作。
  • 不同步的唯一好處:處理速度快,是多個線程併發執行的。
2 同步處理
  • 同步就是指讓所有的線程不是一起進入到方法中去,而是按順序一個一個來。
2.1 synchronized處理同步
  • 使用synchronized關鍵字實現內建鎖處理,有兩種模式:同步代碼塊、同步方法

  • 使用同步代碼塊:

    • 必須要設置一個鎖定對象,一般可鎖定當前對象this
    • 在方法裏使用synchronized(this)攔截,進入到方法中的線程仍可能有多個
  • 使用同步方法:

    • 將關鍵字加在方法聲明上:public synchronized void sale(){}
  • 同步雖可以保證數據完整性(線程安全操作),但其執行速度會很慢。

  • 對象鎖的概念:

    • 實際上,synchronized(this)以及非static的synchronized方法,只能防止多個線程同時執行同一個對象的同步代碼段;即synchronized鎖住的是括號裏的對象,而非代碼。
    • 當synchronized鎖住一個對象後,別的線程若想拿到此對象鎖,只能等此線程執行完成釋放鎖,才能再次給對象加鎖,達到線程同步,一個一個執行。
  • 那麼,若想鎖住這段代碼如何做?
    ① 鎖住同一個對象;
    ② 鎖這個類對應的Class對象(常用)

  • 即:synchronized(Sync.class)實現全局鎖的效果,鎖住類而非對象。
    另外 static synchronized的方法也相當於全局鎖,因爲被static修飾。

2.2 synchronized實現原理

synchronized關鍵字使用在方法和代碼塊中的場景如下表:在這裏插入圖片描述

  • 1 同步代碼塊的底層實現原理:

    • 執行同步代碼塊後首先執行monitorenter指令,退出時monitorexit指令。
    • 使用synchronized進行同步,必須要獲取到對象的監視器monitor,才能向下執行,否則只能等待;但同一時刻只有一個線程能獲取到monitor
    • 同步代碼塊的字節碼文件中包含一個monitorenter和多個monitorexit,因爲JVM需確保所獲得的鎖在正常或異常執行路徑上都能夠被解鎖。
  • 2 同步方法的底層實現原理:

    • 當用synchronized修飾方法時,字節碼中方法的訪問標記包括ACC_SYNCHRONIZED,該標記表示進入該方法時,JVM需進行monitorenter操作,而在退出方法時,不論正常或異常,都需monitorexit操作。
    • 此處的monitor操作對應的鎖對象是隱式的,對於實例方法是this;對於靜態方法是Class實例。
  • monitorenter、monitorexit的作用:

    • 將其理解爲每個鎖對象擁有一個鎖計數器和一個指向持有該鎖的線程的指針。
    • 當執行monitorenter時,
      • 若目標鎖對象的計數器爲0,則說明其未被線程持有;此情況JVM將該鎖對象的持有線程設置爲當前線程,計數器+1;
      • 若目標鎖對象的計數器不爲0,且持有線程是當前線程,則JVM將其計數器+1,否則需等待,直到持有線程釋放該鎖。
    • 當執行monitorexit時,
      • JVM將鎖對象的計數器-1,當減爲0時,代表該鎖已被釋放。
    • 採用此種計數器方式,是爲了允許同一個線程重複獲取同一把鎖,即:鎖的可重入性。

死鎖

  • 在JDK1.5中,synchronized性能很低,因爲這是一個重量級操作,對性能最大的影響是阻塞的實現,掛起線程和回覆線程的操作都需轉入內核態完成,給系統的併發性帶來很大壓力,相比之下Lock對象,性能更高些。
  • 但在JDK1.6中,對synchronized加入了很多優化措施,性能極大提高,優先考慮用synchronized。
  • 下一篇博文中總結了synchronized的優化細節。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章