JUC併發程式設計基石AQS原始碼之結構篇

LuxBai發表於2020-04-19

前言

AQS(AbstractQueuedSynchronizer)算是JUC包中最重要的一個類了,如果你想了解JUC提供的併發程式設計工具類的程式碼邏輯,這個類絕對是你繞不過的。我相信如果你是第一次看AQS原始碼肯定是一臉懵逼,一個個方法跳來跳去一會就繞蒙了。所以把整個程式碼骨架搞明白是你看懂AQS原始碼的第一步。本篇文章只說程式碼結構,之後的篇章會講解AQS具體的執行邏輯。

頂級介面Lock

public interface Lock {

    void lock();
  
    void unlock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    Condition newCondition();
}

我們一直強調面向介面程式設計的思想,看原始碼也是先從介面入手。Lock算是JUC提供的鎖中的頂級介面,我們平常使用的ReentrantLockReadWriteLock等直接或間接的都實現了這個介面。這個介面主要是用來規定我們怎樣去加鎖和解鎖,將加鎖和解鎖的方法暴露出去。下面的虛擬碼是一個典型的加鎖解鎖方式,多麼簡單,這就是面向介面的藝術。

 Lock l = ...;
 l.lock();
 try {
   // 自己的邏輯程式碼
 } finally {
   l.unlock();
 }

程式碼結構

到現在我們知道了怎麼加鎖和解鎖的方式,下一步自己通過介面去實現一個加鎖類。這個比你直接看原始碼更重要。Doug Lea(AQS原始碼作者)大神在AQS類註解上就給我們提供了一個示例類Mutex看原始碼類註解和方法註解也是你理解原始碼的一個重要渠道,還可以鍛鍊自己的英文。

實現Lock介面

首先要實現Lock介面,將加鎖和解鎖方法暴露出去。我們以加鎖為例

class Mutex implements Lock, java.io.Serializable {

    public void lock() {
        sync.acquire(1);
    }

程式碼很簡單,呼叫了sync.acquire(1)方法,所以這個方法是加鎖邏輯的入口,往下看

內部類繼承AQS

現在將加鎖方法暴露出去了,具體的加鎖邏輯則需要AQS類了。AQS是一個抽象類,我們要使用它則需要繼承,實現抽象方法。AQS加鎖採用的是模板的設計模式加鎖以及鎖失敗的後續處理的整體流程程式碼已經實現,我們只需要實現我們需要的具體加鎖方式即可。

class Mutex implements Lock, java.io.Serializable {

   // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();
  
    // 內部類繼承AQS
    private static class Sync extends AbstractQueuedSynchronizer {

        // 實現抽象方法
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }  
    }
}

我們會發現Sync繼承了AQS,但是沒有sync.acquire()這個方法,那麼這個方法肯定來源於父類了。

AQS的acquire方法

public abstract class AbstractQueuedSynchronizer{
  
  //加鎖的入口方法,模板的設計模式
  public final void acquire(int arg) {
      if (!tryAcquire(arg) &&
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          selfInterrupt();
  }
	
  //具體的加鎖邏輯,需要自己實現
	protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

acquire方法定義了整個的加鎖流程,而且使用了設計模式中的模板模式。

整體呼叫流程

從上面的流程可以看出,就這四個步驟就涉及到了三個類

  • Mutex.lock為暴露出去的加鎖方法

  • AQS.acquire是加鎖的模板方法,實現了加鎖邏輯的整個流程

  • Sync.tryAcquire,圖中的綠色部分,是一個抽象方法需要自己實現,針對不同的鎖型別如公平鎖、非公平鎖、共享鎖、獨佔鎖等有不同的實現方式。

  • AQS.acquireQueued是加鎖失敗後的邏輯,將執行緒入隊,這個後面講AQS原始碼會重點講。

    解鎖操作的流程和加鎖類似,讀者可以自己看一下解鎖的流程。

自定義加鎖類的原始碼

下面的程式碼是Mutex類的整體程式碼,有需要的可以在自己的IDE中感受一下整體的結構。

class Mutex implements Lock, java.io.Serializable {

    public static void main(String[] args) {
        Lock lock = new Mutex();
        lock.lock();
        try {

        }finally {
            lock.unlock();
        }
    }

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    // Our internal helper class
    private static class Sync extends AbstractQueuedSynchronizer {
        // Reports whether in locked state
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // Acquires the lock if state is zero
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // Releases the lock by setting state to zero
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // Provides a Condition
        Condition newCondition() {
            return new ConditionObject();
        }

        // Deserializes properly
        private void readObject(ObjectInputStream s)
                throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

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

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

下一篇我們將根據上面所說的來分析ReentrantLock類的程式碼結構

如有不實,還望指正

相關文章