通过ArrayBlockingQueue源码简析一个AQS和多个Condition工作机制

在学习ReentrantLock时会不会有这样的问题,为并发包中的Lock啥要支持多个等待队列?

为啥ArrayBlockingQueue是一个锁,两个Condition?

ReentrantLock里的sync变量(FairSync、NonfairSync都是继承了Sync,Sync继承了AbstractQueuedSynchronizer),AbstractQueuedSynchronizer的内部类ConditionObject实现了Condition接口

 public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
......

这里的ArrayBlockingQueue源码不是全部的,只是通过主要源码分析阻塞队列的消费者-生产者模型机制。 

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /** The queued items */
    final Object[] items;
   
    //这里通过takeIndex、putIndex下标将items数组做成环状数组,
    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    /** Number of elements in the queue */
    int count;

    /** Main lock guarding all access ,通过lock控制获取元素、存放元素 */
    final ReentrantLock lock;

    /** Condition for waiting takes ,等待获取元素条件*/
    private final Condition notEmpty;

    /** Condition for waiting puts ,等待存放元素条件*/
    private final Condition notFull;
    
    //从队列中获取一个元素
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果数组中元素个数为0,则该要从数组中获取元素的消费者线程阻塞在notEmpty条件上
            while (count == 0)
                notEmpty.await();
            //数组中的元素个数不为0,则返回获取元素
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
   /**
     * 提取元素,移动takeIndex下标,唤醒一个生成者线程,将生产的元素放入到数组中
     * Extracts element at current take position, advances, and signals.
     * Call only when holding lock.
     */
    private E dequeue() {
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

    //向数组中存放一个生产出来的元素e
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果数组已满,则该生产者线程阻塞在notFull条件上
            while (count == items.length)
                notFull.await();
            //向数组中插入元素
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
    
    private static void checkNotNull(Object v) {
        if (v == null)
            throw new NullPointerException();
    }
    /**
     * 插入元素,移动putIndex下标,唤醒阻塞在notEmpty上的消费者线程
     * Inserts element at current put position, advances, and signals.
     * Call only when holding lock.
     */
    private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

消费者线程是等待在notEmpty条件上,生产者是等待在notFull条件上,这样就比较明确的知道等待在某个条件上的线程的功能是啥,能够准确的唤醒需要某个功能(如生产元素、消费元素)的线程,让其继续工作。

为啥ArrayBlockingQueue是一个锁呢?因为items数组、takeIndex、putIndex、count都是普通变量,线程从条件状态唤醒后,必须获得与Condition关联的锁,这样线程进行这些变量的操作都是线程安全的。

    /**
     * Wakes up one waiting thread.
     * 唤醒一个等待在Condition上的线程。该线程从等待方法返回前必须获得与Condition关联的锁
     */
    void signal();

如果使用synchronized来实现阻塞队列的话,则需要使用两个对象加锁,一个锁对象控制生产、一个锁对象控制消费;

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