1. 程式人生 > >併發程式設計(三)—— ReentrantLock實現原理及原始碼分析

併發程式設計(三)—— ReentrantLock實現原理及原始碼分析

  ReentrantLock是Java併發包中提供的一個可重入的互斥鎖ReentrantLocksynchronized在基本用法,行為語義上都是類似的,同樣都具有可重入性。只不過相比原生的Synchronized,ReentrantLock增加了一些高階的擴充套件功能,比如它可以實現公平鎖,同時也可以繫結多個Conditon

可重入性/公平鎖/非公平鎖

可重入性

      所謂的可重入性,就是可以支援一個執行緒對鎖的重複獲取,原生的synchronized就具有可重入性,一個用synchronized修飾的遞迴方法,當執行緒在執行期間,它是可以反覆獲取到鎖的,而不會出現自己把自己鎖死的情況。ReentrantLock也是如此,在呼叫lock()方法時,已經獲取到鎖的執行緒,能夠再次呼叫lock()方法獲取鎖而不被阻塞。

公平鎖/非公平鎖

  所謂公平鎖,顧名思義,意指鎖的獲取策略相對公平,當多個執行緒在獲取同一個鎖時,必須按照鎖的申請時間來依次獲得鎖,排排隊,不能插隊;非公平鎖則不同,當鎖被釋放時,等待中的執行緒均有機會獲得鎖。synchronized是非公平鎖,ReentrantLock預設也是非公平的,但是可以通過帶boolean引數的構造方法指定使用公平鎖,但非公平鎖的效能一般要優於公平鎖。

  synchronized是Java原生的互斥同步鎖,使用方便,對於synchronized修飾的方法或同步塊,無需再顯式釋放鎖。而ReentrantLock做為API層面的互斥鎖,需要顯式地去加鎖解鎖。採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。

class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...
 
    public void m() {
      lock.lock();  // 加鎖
      try {
        // ... 函式主題
      } finally {
        lock.unlock() //解鎖
      }
    }
}

原始碼分析

  接下來我們從原始碼角度來看看ReentrantLock的實現原理,它是如何保證可重入性,又是如何實現公平鎖的。

1、無參構造器(預設為非公平鎖)

public ReentrantLock() {
     sync = new NonfairSync();//預設是非公平的
}

sync是ReentrantLock內部實現的一個同步元件,它是Reentrantlock的一個靜態內部類,繼承於AQS。

2、帶布林值的構造器(是否公平)

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();//fair為true,公平鎖;反之,非公平鎖
}

此處可以指定是否採用公平鎖,FailSync和NonFailSync亦為Reentrantlock的靜態內部類,都繼承於Sync

3、lock()

public void lock() {
        sync.lock();//代理到Sync的lock方法上
}

Sync的lock方法是抽象的,實際的lock會代理到FairSync或是NonFairSync上(根據使用者的選擇來決定,公平鎖還是非公平鎖)

4、unlock()

public void unlock() {
        sync.release(1);//釋放鎖
}

釋放鎖,呼叫sync的release方法。

5、tryLock()

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //處理任務
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //釋放鎖
     } 
}else {
    //如果不能獲取鎖,則直接做其他事情
}

tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false。

6、newCondition()

public Condition newCondition() {
        return sync.newCondition();
}

獲取一個conditon,ReentrantLock支援多個Condition

7、await()

public class MyService {

    private Lock lock = new ReentrantLock();
    private Condition condition=lock.newCondition();
    public void testMethod() {
        
        try {
            lock.lock();
            System.out.println("開始wait");
            condition.await();
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName=" + Thread.currentThread().getName()
                        + (" " + (i + 1)));
            }
        } catch (InterruptedException e) {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }

}

通過建立Condition物件來使執行緒wait,必須先執行lock.lock方法獲得鎖

8、signal()

public void signal() {
        try {
            lock.lock();
            condition.signal();
        } finally {
            lock.unlock();
        }
}

condition物件的signal方法可以喚醒wait執行緒

9、建立多個condition物件

  一個condition物件的signal(signalAll)方法和該物件的await方法是一一對應的,也就是一個condition物件的signal(signalAll)方法不能喚醒其他condition物件的await方法

 

ABC迴圈列印20遍

  1 package main.java.Juc;
  2 
  3 import java.util.concurrent.locks.Condition;
  4 import java.util.concurrent.locks.Lock;
  5 import java.util.concurrent.locks.ReentrantLock;
  6 
  7 /*
  8  * 編寫一個程式,開啟 3 個執行緒,這三個執行緒的 ID 分別為 A、B、C,每個執行緒將自己的 ID 在螢幕上列印 10 遍,要求輸出的結果必須按順序顯示。
  9  *    如:ABCABCABC…… 依次遞迴
 10  */
 11 public class TestABCAlternate {
 12     
 13     public static void main(String[] args) {
 14         AlternateDemo ad = new AlternateDemo();
 15         
 16         new Thread(new Runnable() {
 17             @Override
 18             public void run() {
 19                 for (int i = 1; i <= 20; i++) {
 20                     ad.loopA(i);
 21                 }
 22             }
 23         }, "A").start();
 24         
 25         new Thread(new Runnable() {
 26             @Override
 27             public void run() {
 28                 for (int i = 1; i <= 20; i++) {
 29                     ad.loopB(i);
 30                 }
 31             }
 32         }, "B").start();
 33         
 34         new Thread(new Runnable() {
 35             @Override
 36             public void run() {
 37                 for (int i = 1; i <= 20; i++) {
 38                     ad.loopC(i);
 39                     System.out.println("-----------------------------------");
 40                 }
 41             }
 42         }, "C").start();
 43     }
 44 
 45 }
 46 
 47 class AlternateDemo{
 48     
 49     private int number = 1; //當前正在執行執行緒的標記
 50     
 51     private Lock lock = new ReentrantLock();
 52     private Condition condition1 = lock.newCondition();
 53     private Condition condition2 = lock.newCondition();
 54     private Condition condition3 = lock.newCondition();
 55     
 56     /**
 57      * @param totalLoop : 迴圈第幾輪
 58      */
 59     public void loopA(int totalLoop){
 60         lock.lock();
 61         try {
 62             //1. 判斷
 63             if(number != 1){
 64                 condition1.await();
 65             }
 66             //2. 列印
 67             for (int i = 1; i <= 1; i++) {
 68                 System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
 69             }
 70             //3. 喚醒
 71             number = 2;
 72             condition2.signal();
 73         } catch (Exception e) {
 74             e.printStackTrace();
 75         } finally {
 76             lock.unlock();
 77         }
 78     }
 79     
 80     public void loopB(int totalLoop){
 81         lock.lock();
 82         try {
 83             //1. 判斷
 84             if(number != 2){
 85                 condition2.await();
 86             }
 87             //2. 列印
 88             for (int i = 1; i <= 1; i++) {
 89                 System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
 90             }
 91             //3. 喚醒
 92             number = 3;
 93             condition3.signal();
 94         } catch (Exception e) {
 95             e.printStackTrace();
 96         } finally {
 97             lock.unlock();
 98         }
 99     }
100     
101     public void loopC(int totalLoop){
102         lock.lock();
103         try {
104             //1. 判斷
105             if(number != 3){
106                 condition3.await();
107             }
108             //2. 列印
109             for (int i = 1; i <= 1; i++) {
110                 System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
111             }
112             //3. 喚醒
113             number = 1;
114             condition1.signal();
115         } catch (Exception e) {
116             e.printStackTrace();
117         } finally {
118             lock.unlock();
119         }
120     }
121     
122 }

執行結果:

程式碼分析:

  三個執行緒分別迴圈20次呼叫loopA、loopB、loopC列印,但是不確定是哪個方法先被呼叫到,如果是loopB先呼叫,則loopB方法先獲取到鎖,loopA和loopC等待鎖,此時執行緒執行標記number=1,程式碼84行處為true,則condition2.await();如果需要喚醒此執行緒,則需要用condition2來喚醒,此時執行緒交出鎖;

  如果loopA獲取了鎖,loopB和loopC等待鎖,此時執行緒執行標記number=1,程式碼63行處為false,則執行67行列印,列印完則用condition2.signal()喚醒列印loopB的執行緒,接著loopB的執行緒去列印B,執行緒loopB列印完畢去喚醒列印loopC的執行緒,列印完loopC再喚醒loopA,如此迴圈20次。

 總結

 1、Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;

 2、synchronized在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

 3、Lock類可以建立Condition物件,Condition物件用來是執行緒等待和喚醒執行緒,需要注意的是Condition物件的喚醒的是用同一個Condition執行await方法的執行緒,所以也就可以實現喚醒指定類的執行緒