Condition 實現原理

Condition 實現原理

說 Codition 前,需要說下 ConditioObject。
ConditionObject 是同步器 AbstractQueuedSynchronzied 的內部類,因爲 Condition 的操作需要關聯的鎖。 ArrayBlockingQueue 就是 Condition 的具體應用。 Object 中其實 也有 wait ,notify ,notifyAll 等操作, Condition 相當於將 wait ,notify ,notifyAll 轉換成想要的對象,將比較難懂的同步操作變成直觀可控的對象行爲。

應用場景 ArrayBlockingQueue

ArrayBlockingQueue 的構造函數。

/** Main lock guarding all access */
final ReentrantLock lock;

/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

通過構造函數,可以看到 Condition 的創建時需要關聯鎖的。

從隊列中去取出(take)數據 。

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

往隊列中加入數據 enqueue

private void enqueue(E x) {
      // assert lock.getHoldCount() == 1;
      // assert items[putIndex] == null;
      final Object[] items = this.items;
      items[putIndex] = x;
      if (++putIndex == items.length)
          putIndex = 0;
      count++;
      notEmpty.signal();
  }

可以看主要用了 await signal 等方法。具體代表什麼含義?

Condition 實現主要包含三個部分:等待隊列、等待、通知

如果瞭解 AQS 原理可以知道, AQS 中有個同步隊列的概念。

等待隊列

等待隊列和同步隊列類似,都是一個 FIFO 隊列。隊列上每個節點包含一個線程引用,該線程就是 Condition 對象上的等待線程。等待隊列結構如下:

等待隊列結構
Condition 等待隊列,也是包含首節點(firstWaiter),和尾節點(tailWaiter),如果一個線程調用了 Condition.await() 方法。那麼該線程將會釋放鎖,並以當前線程構造節點加入等待隊列並進入等待狀態。

Object 監視器模型

Object 監視器模型 包含了一個同步多路和多個等待隊列,結構如下所示:

同步隊列和等待隊列

等待

當調用 Condition 的 await() 方法(或者以 await開頭的方法),會使得當前線程進入等待隊列,並且釋放鎖,同時線程的狀態變爲等待狀態。

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 當前線程加入等待隊列  
            Node node = addConditionWaiter();
            // 釋放同步狀態,也就是釋放鎖
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // node 不在節點中會一直 park 阻塞下去。達到等待的效果。
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

調用該方法的線程成功獲得了鎖的線程,也就是同步隊列的首節點,該方法將會將該線程構造成節點並加入等待隊列中,然後釋放同步狀態,喚醒同步隊列中的後續節點,然後當前節點會進入等待狀態。要注意的是,如果等地隊列中的節點被喚醒,喚醒節點的線程開始嘗試獲取同步狀態。但是如果不是通過 Condition.signal 進行喚醒的,而是對等待線程進行中斷,那麼會拋出 InterruptedException。

調用 Condition signal 方法後,當前線程會加入到等待隊列,如下圖所示:
當前線程加入等待隊列

通知

調用 Condition.signal() 方法,將會喚醒等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移動到同步隊列中。

  public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
  final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

需要注意的是,調用該方法的前置條件是當前線程必須獲得了鎖,可以看到 Signal() 方法進行了
isHeldExclusively 檢查,判斷是否獲得了鎖,接着獲取等待隊列的首節點,將其移動到同步隊列並使用 LockSupport 喚醒節點中的線程。

節點從等待隊列,移動到同步隊列的操作過程如下:

image

通過調用同步器的 enq(Node node) 方法,等待隊列中的頭節點線程安全地移動到同步隊列中,當節點移動到同步隊列後,當前線程再使用 LockSupport 喚醒該節點的線程。

被喚醒的線程,將從 await() 方法中的 while 循環中退出。從 await 方法看

      //  當前節點已經在同步隊列了,不會在循環下去了
      while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

隨後調用同步器的 acquireQueued() 方法加入到同步隊列的競爭中。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

成功獲取同步狀態(獲得鎖)之後,被喚醒的線程,景從先前調用的 await 方法返回。此時線程已經成功獲得了鎖。

總結

本文剖析了一下 Condition 的實現原理,等待隊列,等待,通知的實現原理。

程序員開發者社區

程序員開發者社區

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