(7)AbstractQueuedSynchronizer和ReentrantLock—— 可重入鎖的實現

目錄

一、 可重入鎖與非可重入鎖

二、 公平鎖與非公平鎖

 

三、獨佔鎖與共享鎖

四、AbstractQueuedSynchronizer(AQS)

五、ReentrantLock

1. lock方法

2. tryAcquire方法

3. 可重入的實現

六、用AQS實現一個自己寫的重入鎖


一、 可重入鎖與非可重入鎖

    可重入鎖,就是當一個佔有鎖的線程再次進入到這個鎖的代碼塊時,可以直接獲得鎖並且執行,而無需等待。

    與之相對的,就是非可重入鎖,當一個佔有鎖的線程再次進入到這個鎖塊時仍然需要等待獲取鎖。

    Java中的synchronized和ReentrantLock都是可重入鎖。

    可重入鎖的優點是:同一個線程獲取同一個鎖時不會造成死鎖,且加快了執行的速度。

    我們舉一個例子來看下可重入鎖:

public class Main {
    public static void funcA(){
        synchronized (Main.class){  //加鎖
            System.out.println("a");
            funcB();
            System.out.println("done");
        }
    }

    public static void funcB(){
        synchronized (Main.class){ //加鎖,同一把鎖
            System.out.println("b");
        }
    }

    public static void main(String[] args) {
        funcA();
    }
}

       上面的代碼,funcA與funcB中加的是同一把鎖,funcA先獲得了鎖開始執行。之後funcA中調用了funcB,funcB同樣也加了鎖,如果鎖不可重入,就會造成死鎖,致使後面的信息打印不出來。而synchronized是可重入鎖,避免了死鎖。

執行結果:

 

二、 公平鎖與非公平鎖

    公平鎖:獲取鎖的機會按照嚴格的請求時間先後順序,有個先來後到。

    非公平鎖:不按照先後順序,誰來了都可以tryAcquire試一下,誰能成功全靠運氣。

    公平鎖與非公平鎖的實現原理

    將需要獲取鎖的線程加入到一個FIFO的隊列中,按照先進先出的原則,每次都是排在第一個的線程擁有獲得鎖的機會。

 

    公平鎖與非公平鎖的比較: 哪一個更好一點呢?

    公平鎖比非公平鎖多用了一個隊列,耗費了一點點內存,但是它可以使所有需要獲取鎖的線程在排隊的時候處於等待狀態(除了排在隊頭的可以處於running),CPU輪詢的時候就無需對這些處於等待狀態的線程進行調度,節省了CPU資源。因此,公平鎖更好一些。

 

三、獨佔鎖與共享鎖

    獨佔鎖,就是一個線程獲取鎖之後,在其釋放鎖前,其他線程就不能獲取這個鎖;

    共享鎖,就是一個線程獲取鎖之後,其他的線程也可以獲取鎖並執行同步代碼塊。

    ReentrantReadWriteLock ,它的寫鎖就是一個獨佔鎖,它的讀鎖就是一個共享鎖。

  •     當上了寫鎖之後,就只允許這一個線程寫,其他線程就不能讀不能寫了;
  •     而當只有讀鎖的時候,多個線程可以共同讀取數據,因爲只讀的時候不會產生線程安全性問題。

 

四、AbstractQueuedSynchronizer(AQS)

    AbstractQueuedSynchronizer內部維護了一個FIFO的隊列和一個 int 狀態變量,隊列用來實現排隊,狀態變量用來控制是否可以獲得鎖。 它爲實現依賴於FIFO隊列的阻塞鎖和相關同步器提供了一個框架。

    AQS實現阻塞鎖通常是通過子類extends AQS來實現的

    AQS定義了很多定義爲final的方法,用來提供服務。

    當子類繼承AQS之後,還有幾個方法需要自己實現,用來完成鎖的獲取和釋放。

tryAcquire(int arg) 嘗試獲取鎖(獨佔)
tryRelease(int arg) 嘗試釋放鎖(獨佔)
tryAcquireShared(int arg) 嘗試獲取鎖(共享)
tryReleaseShared(int arg) 嘗試釋放鎖(共享)
isHeldExclusively 返回boolean, 是否被獨佔

如果是實現獨佔鎖,就實現前兩個方法;

如果是實現共享鎖,就實現後兩個方法。

這些個方法的原始寫法只是拋出一個異常,需要繼承者自己實現。

 

   AQS中的其他方法及其作用下面以ReentrantLock的實現爲例來講解。 

五、ReentrantLock

    ReentrantLock是通過繼承AQS來實現的,它的類結構圖是這樣

    ReentrantLock中包含了一個Sync類,Sync類繼承了AQS的諸多方法。

    NonFairSync 和 FairSync繼承了Sync類,它們兩個是真正用來幹活的,它們各有兩個方法 lock 和 tryAcquire用來實現獲取鎖,FairSync實現公平鎖,NonFairSync實現非公平鎖。

    看一下ReentrantLock的源碼實現

    

1. lock方法

    非公平鎖先嚐試去獨佔,如果不成功再和公平鎖一樣調用AQS的acquire()方法。在acquire()方法中,調用了addWaiter,正是addWaiter方法中將想要獲取鎖的線程加入了FIFO的Queue中。

        

2. tryAcquire方法

    非公平鎖直接調用Sync類的nonfairTryAcquire方法,其實nonfairTryAcquire的實現與公平鎖的tryAcquire差不多,只是多加了一個判斷線程隊列是否爲空的步驟而已。

    如果線程隊列不爲空又不是同一個線程重入,則直接return false,會使處於Queue的頭節點的線程優先獲取鎖,從而實現獲取時間上的公平。

3. 可重入的實現

  在tryAcquire方法中,else if 判斷如果當前線程是已經獨佔了鎖的線程,那麼只是將鎖的層數+1,然後返回true,就相當於又獲取了鎖,從而實現了可重入。

 

六、用AQS實現一個自己寫的重入鎖

  

import javax.management.RuntimeErrorException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock{  //implements Lock,聲明MyLock爲鎖
    private static Helper helper = new Helper();
    private static class Helper extends AbstractQueuedSynchronizer{  //同Sync一樣extends AQS實現功能
        @Override
        protected boolean tryAcquire(int arg) {
            int state = getState();
            if(0 == state){
                if(compareAndSetState(arg)){
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            }
            //實現可重入
            else if(getExclusiveOwnerThread() == Thread.currentThread()){
                setState(state + arg);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            //容錯處理
            if (getExclusiveOwnerThread() != Thread.currentThread()){
                throw new RuntimeException();
            }

            int state = getState() - arg;
            if (state == 0){
                setExclusiveOwnerThread(null);
                setState(state);      //這裏還有下面都有setState(state)但不能合併提前到前面,否則先setState(state)就會造成釋放另一個線程先運行,造成原子性失敗
                return true;
            }
            setState(state);     //setState(state)不能合併提前到前面
            return false;
        }

        @Override
        protected boolean isHeldExclusively() {
            return 0 != getState();
        }

        protected Condition getCondition(){
            return new ConditionObject();
        }
    }
    @Override
    public void lock() {
        helper.acquire(1);
    }

    @Override
    public void unlock() {
        helper.release(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        helper.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return helper.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return helper.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public Condition newCondition() {
        return helper.getCondition();
    }
}

  測試代碼

public class Main {
    private static volatile int num = 0;
    private static MyLock lock = new MyLock();

    public static void main(String[] args) {
        //起兩個線程交替打印確定不會出現線程安全性問題
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    if(lock.tryLock()){
                        ++num;
                        System.out.println(Thread.currentThread().getName() + " " + num);
                        lock.unlock();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    if(lock.tryLock()){
                        System.out.println(Thread.currentThread().getName() + " " + num);
                        ++num;
                        lock.unlock();
                    }
                }
        }
        }).start();
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章