JUC之Lock介面以及Synchronized回顧

xbhog發表於2021-12-17

Lock介面

Synchronized關鍵字回顧:

多執行緒程式設計步驟(上):

  1. 建立資源類,在資源類建立屬性和操作方法
  2. 建立多個執行緒,呼叫資源類的操作方法

建立執行緒的四種方式:

  1. 繼承Thread
  2. 實現Runnable介面
  3. 使用Callable介面
  4. 使用執行緒池

使用synchronized同步實現售票問題:

只有當資源是空閒的時候,執行緒才能訪問。

 /**
  * 建立資源
  */
 class ticket{
     private  int number = 30;
     public synchronized void sell(){
         if(number >0){
             System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩餘:"+number);
         }
 ​
     }
 }
 ​
 /**
  * 建立多個執行緒,呼叫資源類的操作方法
  */
 public class sellTicket {
     public static void main(String[] args) {
         /**
          * 建立資源
          */
         ticket ticket = new ticket();
         /**
          * 建立三個執行緒(售票員)
          */
         new Thread(new Runnable() {
             public void run() {
                 for (int i = 0; i < 40; i++) {
                     ticket.sell();
                 }
             }
         }, "aaa").start();
         new Thread(new Runnable() {
             public void run() {
                 for (int i = 0; i < 40; i++) {
                     ticket.sell();
                 }
             }
         }, "bbb").start();
         new Thread(new Runnable() {
             public void run() {
                 for (int i = 0; i < 40; i++) {
                     ticket.sell();
                 }
             }
         }, "ccc").start();
     }
 }
 ​

學習《Java併發程式設計的藝術》一節是synchronized的實現原理與應用

Java中的每一個物件都可以作為鎖:

  1. 對於同步方法,鎖的是當前的物件。
  2. 對於靜態方法,鎖的是當前類的class物件。
  3. 對於靜態程式碼塊,Synchonized括號裡配置的物件。

當一個執行緒試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常時必須釋放鎖。

那麼鎖到底存在哪裡呢?鎖裡面會儲存什麼資訊呢?

這個問題是該書中的一個問題,對個人來說有點抽象,主要點是:JVM基於進入和退出Monitor(監視器)物件來實現方法同步和程式碼塊同步,但兩者的實現細節不一樣。

程式碼塊同步是使用monitorenter 和monitorexit指令實現的,而方法同步是使用另外一種方式實現的,細節在JVM規範裡並沒有 詳細說明。但是,方法的同步同樣可以使用這兩個指令來實現。

monitorenter指令是在編譯後插入到同步程式碼塊的開始位置,而monitorexit是插入到方法結 束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何物件都有 一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。執行緒執行到monitorenter 指令時,將會嘗試獲取物件所對應的monitor的所有權,即嘗試獲得物件的鎖。

其中monitorenter主要是獲取監視器鎖,monitorexit主要是釋放監視器鎖。

synchonized中的一個概念是Java物件頭:

synchronized用的鎖是存放在Java物件頭裡面的。

非陣列型別 陣列型別
虛擬機器 3個字寬 2個字寬

需要注意的是:在32位虛擬機器中:1字寬=4位元組,即:32bit

Java物件頭裡的Mark Word裡面預設儲存物件HashCode、分代年齡和鎖標記位。

在執行期間,Mark Word裡面儲存的資料會隨著鎖標誌位的變化而變化。

32位JVM的Mark Word的預設儲存結構與在64位JVM不相同

Lock介面並建立例項:

Java併發程式設計的藝術

在JavaSE5之前使用的是synchronized關鍵字實現鎖功能,5v之後併發包中新增Lock介面以及相關實現類用來實現鎖功能,提供與synchronized類似的同步關係,但是比其更具有靈活性和擴充套件性(擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖)。

檢視Lock的文件得知:

使用 lock 塊來呼叫 try,在之前/之後的構造中,最典型的程式碼如下:

  class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...
 ​
    public void m() { 
      lock.lock();  // block until condition holds
      try {
        // ... method body
      } finally {
        lock.unlock()
      }
    }
  }
  

根據上述的程式碼,實現買票程式碼:

 import java.util.concurrent.locks.ReentrantLock;
 class ticket2{
     private int number = 30;
 ​
     //建立一個重入鎖
     private ReentrantLock lock = new ReentrantLock();
 ​
     public void sale(){
         //在內容之前加鎖
         lock.lock();
         if(number >0){
             System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩餘:"+number);
         }
         //在內容之後解鎖
         lock.unlock();
     }
 }
 ​
 public class lockCase {
     public static void main(String[] args) {
         ticket2 tk = new ticket2();
         //建立執行緒
         new Thread(()->{
             for (int i = 0; i < 40; i++) {
                 tk.sale();
             }
         },"aaa").start();
         new Thread(()->{
             for (int i = 0; i < 40; i++) {
                 tk.sale();
             }
         },"bbb").start();
         new Thread(()->{
             for (int i = 0; i < 40; i++) {
                 tk.sale();
             }
         },"bbb").start();
     }
 }

上述程式碼中,在ticket2類中存在一個問題就是,當lock中間內容如果出現報錯,那麼後面的程式碼無法執行,也就是鎖無法釋放。所以我們需要將unlock()放到finally中。

目的是保證在獲取到鎖之後,最終能夠被釋放。

 public void sale(){
         //在內容之前加鎖
         lock.lock();
         try{
             if(number >0){
                 System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩餘:"+number);
             }
             //在內容之後解鎖
         }finally{
             lock.unlock();
         }
 ​
     }

相關文章