Condition實現原理

生活咖啡發表於2021-07-19

Condition介面提供了與Object阻塞(wait())與喚醒(notify()notifyAll())相似的功能,只不過Condition介面提供了更為豐富的功能,如:限定等待時長等。Condition需要與Lock結合使用,需要通過鎖物件獲取Condition

一、基本使用

基於Condition實現生產者、消費者模式。程式碼基本與Object#wait()Object#notify()類似,只不過我們使用Lock替換了synchronized關鍵字。
生產者

public class Producer implements Runnable {
    private Lock lock;
    private Condition condition;
    private Queue<String> queue;
    private int maxSize;

    public Producer(Lock lock, Condition condition, Queue<String> queue, int maxSize) {
        this.lock = lock;
        this.condition = condition;
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        int i = 0;
        for (; ; ) {
            lock.lock();
            // 如果滿了,則阻塞
            while (queue.size() == maxSize) {
                System.out.println("生產者佇列滿了,等待...");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            queue.add("一個訊息:" + ++i);
            System.out.printf("生產者%s生產了一個訊息:%s\n", Thread.currentThread().getName(), i);
            condition.signal();
            lock.unlock();
        }
    }
}

消費者

public class Consumer implements Runnable {
    private Lock lock;
    private Condition condition;
    private Queue<String> queue;
    private int maxSize;

    public Consumer(Lock lock, Condition condition, Queue<String> queue, int maxSize) {
        this.lock = lock;
        this.condition = condition;
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        for (; ; ) {
            lock.lock();
            while (queue.isEmpty()) {
                System.out.println("消費者佇列為空,等待...");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String obj = queue.remove();
            System.out.printf("消費者%s消費一個訊息:%s\n", Thread.currentThread().getName(), obj);
            condition.signal();
            lock.unlock();
        }
    }
}

測試類

public class ConditionProducerConsumer {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Queue<String> queue = new LinkedBlockingQueue<>();
        int maxSize = 10;

        Producer producer = new Producer(lock, condition, queue, maxSize);
        Consumer consumer = new Consumer(lock, condition, queue, maxSize);

        new Thread(producer).start();
        new Thread(consumer).start();

    }
}

二、原始碼分析

上述示例中使用的LockReentrantLock,關於它的lock方法與unlock方法的原理詳見ReentrantLock實現原理。上述示例中的Condition物件是呼叫了Lock#newCondition()方法,原始碼如下:

public class ReentrantLock implements Lock, java.io.Serializable {
	...
	public Condition newCondition() {
        return sync.newCondition();
    }
	
	abstract static class Sync extends AbstractQueuedSynchronizer {
		...
		final ConditionObject newCondition() {
            return new ConditionObject();
        }
		...
	}
	...
}

上述的ConditionObject定義在AQS中,如下:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	...
	public class ConditionObject implements Condition, java.io.Serializable {
		...
	}
	...
}

首先來分析下Condition#await()方法

public final void await() throws InterruptedException {
	if (Thread.interrupted())
		throw new InterruptedException();
	Node node = addConditionWaiter();
	int savedState = fullyRelease(node);
	int interruptMode = 0;
	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);
}

private Node addConditionWaiter() {
	Node t = lastWaiter;
	// If lastWaiter is cancelled, clean out.
	if (t != null && t.waitStatus != Node.CONDITION) {
		unlinkCancelledWaiters();
		t = lastWaiter;
	}
	Node node = new Node(Thread.currentThread(), Node.CONDITION);
	if (t == null)
		firstWaiter = node;
	else
		t.nextWaiter = node;
	lastWaiter = node;
	return node;
}

根據AQS佇列的特性,若有多個執行緒執行lock#lock()方法,會將處於阻塞狀態的執行緒維護到一個雙向連結串列中,如下:
image
假設當前是執行緒A獲取到鎖,其他執行緒執行lock#lock()方法時,將會構建成一個上述連結串列。
若獲取鎖的執行緒(執行緒A)執行Condition#await()方法,則會將當前執行緒新增至Condition佇列中,如下:
image
然後在呼叫fullyRelease()方法時會釋放當前執行緒的鎖,然後喚醒處於阻塞佇列中的下一個執行緒:
image
在呼叫isOnSyncQueue()方法時會檢查當前節點是否在同步佇列中,若不存在,則會呼叫LockSupport.park()進行阻塞。

假設當前執行緒A是生產者執行緒,呼叫await()方法後,會釋放鎖,並且將當前執行緒加入到Condition佇列中。此時,消費者能獲取到鎖資源,然後繼續執行。假設執行緒B是消費者執行緒,當新增一個元素後會呼叫condition#signal()方法,定義如下:

public final void signal() {
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	Node first = firstWaiter;
	if (first != null)
		doSignal(first);
}

private void doSignal(Node first) {
       do {
           if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
           first.nextWaiter = null;
           } while (!transferForSignal(first) &&
                (first = firstWaiter) != null);
}

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()方法,會將Condition佇列中的第一個節點移除,將其變為同步佇列中的尾結點,如下:
image
至此,完成了Condition佇列轉換為同步佇列的過程。後續流程基本就是重複以上操作。

本文詳細介紹了單個Condition佇列的執行流程,其實一個Lock中可以有多個Condition佇列,比如:JUC中提供的LinkedBlockingDequeArrayBlockingQueue

相關文章