JDK源码解析之ArrayBlockingQueue

前言:

    之前介绍的关于Queue的实现类,LinkedList、PriorityQueue都是非线程安全的队列,那么有没有线程安全的Queue实现类呢?

    当然是有的,在java.util.concurrent中有几个关于Queue的线程安全实现

/**
 * @see java.util.concurrent.ArrayBlockingQueue
 * @see java.util.concurrent.LinkedBlockingQueue
 * @see java.util.concurrent.PriorityBlockingQueue
 */

    由名称可以看出来,ArrayBlockingQueue是基于数组实现的;LinkedBlockingQueue是基于链表实现的;PriorityBlockingQueue是基于堆实现的优先级队列    

    实现方式都是差不多的,只是底层结构不一样,之前的几篇博客已经分析过这些数据结构的不同了,在这里就不再继续分析了,焦点就关注在如何实现线程安全。

    本篇博客来分析一下比较典型的ArrayBlockingQueue

1.ArrayBlockingQueue结构分析

/**
 * A bounded {@linkplain BlockingQueue blocking queue} backed by an
 * array.  This queue orders elements FIFO (first-in-first-out).
 */
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
        
    // 基于数组存储
    /** The queued items */
    final Object[] 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 */
    final ReentrantLock lock;

    // 锁的条件队列(非空,如果队列已空,则再执行删除元素的是会在此等待)
    /** Condition for waiting takes */
    private final Condition notEmpty;

    // 锁的条件队列(未满,如果队列已满,则再执行添加元素的是会在此等待)
    /** Condition for waiting puts */
    private final Condition notFull;

    笔者把对应的英文注释也拷贝过来了,实际直接看英文注释基本就已经理解了相关成员变量的含义了

2.构造方法

// 1.指定容量构造
public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

// 2.指定容量和锁的类型
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();
}

// 3.指定容量、锁的类型、队列初始值
public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    this(capacity, fair);

    final ReentrantLock lock = this.lock;
    lock.lock(); // Lock only for visibility, not mutual exclusion
    try {
        int i = 0;
        try {
            for (E e : c) {
                checkNotNull(e);
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        count = i;
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        lock.unlock();
    }
}

    构造方法主要是初始化锁和锁的条件判断

3.添加操作

    1)ArrayBlockingQueue.add(E e)

// ArrayBlockingQueue.add()
public boolean add(E e) {
    // 直接调用父类的add方法
    return super.add(e);
}

// AbstractQueue.add()
public boolean add(E e) {
    // 又直接调用了子类的offer方法
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

// ArrayBlockingQueue.offer()
public boolean offer(E e) {
    // 1.判断对象是否为空,如果为空,则直接抛错
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 2.获取锁对象
    lock.lock();
    try {
        // 3.如果数组已满,则直接返回false
        if (count == items.length)
            return false;
        else {
            // 4.执行添加操作
            enqueue(e);
            return true;
        }
    } finally {
        // 释放锁
        lock.unlock();
    }
}

// ArrayBlockingQueue.enqueue()
private void enqueue(E x) {
    
    final Object[] items = this.items;
    // 1.直接在putIndex上存放该值x
    items[putIndex] = x;
    // 2.如果putIndex+1之后等于数组长度,说明当前添加的元素占据了数组的最后一个位置
    // 就需要进行一次轮回,将putIndex重新置为0
    if (++putIndex == items.length)
        putIndex = 0;
    // 4.count自增
    count++;
    // 5.这一步很关键,可能有其他线程在执行获取元素的时候,停在了notEmpty.await()上了
    // 所以需要将其唤醒
    notEmpty.signal();
}

    2)ArrayBlockingQueue.offer(E e)如上所示

    3)ArrayBlockingQueue.offer(E e, long timeout, TimeUnit unit)与offer(E e)方法最大的区别就是执行lock.lock()方法时是有时限的

    4)ArrayBlockingQueue.put(E e)

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    // 1.获取锁对象,
    // lockInterruptibly是可被中断的
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 2.如果队列已满,则执行await方法,在这里等待
        // 一直到有线程执行notFull.signal或者当前线程中断
        while (count == items.length)
            notFull.await();
        // enqueue()方法上面已经分析过
        enqueue(e);
    } finally {
        // 3.释放对象锁
        lock.unlock();
    }
}

    有关于Condition.await()方法,我们需要注意的是,它会自动释放获取的对象锁,转而将当前线程挂起,直到其他线程执行Condition.signal()或者当前线程被中断

    总结:关于添加的这三个方法

    * add 队列已满时会抛出异常

    * offer 队列已满时返回false,执行成功则返回true,针对于null值的添加抛出NullPointException

    * put 队列已满时则阻塞

4.删除操作

    1)ArrayBlockingQueue.remove(Object o)

public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    // 1.获取锁对象
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count > 0) {
            final int putIndex = this.putIndex;
            // 2.从takeIndex向putIndex遍历,因为只有这段索引之间有数据
            // 查询到对应的值时,执行删除操作
            int i = takeIndex;
            do {
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
        // 3.释放对象锁
        lock.unlock();
    }
}

// ArrayBlockingQueue.removeAt(int index)
void removeAt(final int removeIndex) {
    final Object[] items = this.items;
    // 1.如果需要删除的刚好是takeIndex所在的那一位,则直接将该位数据置空即可
    if (removeIndex == takeIndex) {
        // removing front item; just advance
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    } else {
        // 2.如果需要删除的数据再takeIndex和putIndex之间,
        // 那么就需要将removeIndex和putIndex之间的数据向前挪一位
        final int putIndex = this.putIndex;
        for (int i = removeIndex;;) {
            int next = i + 1;
            if (next == items.length)
                next = 0;
            if (next != putIndex) {
                items[i] = items[next];
                i = next;
            } else {
                items[i] = null;
                this.putIndex = i;
                break;
            }
        }
        count--;
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    // 3.删除完之后,唤醒那些添加时阻塞的线程
    notFull.signal();
}

    2)ArrayBlockingQueue.poll()

public E poll() {
    // 1.获取锁对象
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 2.如果队列为空则直接返回null,否则执行dequeue()
        return (count == 0) ? null : dequeue();
    } finally {
        
        // 3.释放对象锁
        lock.unlock();
    }
}

// dequeue()
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    // 1.记得需要满足队列的特性,先进先出,
    // 所以获取元素,takeIndex从0开始
    E x = (E) items[takeIndex];
    // 获取之后将takeIndex所在元素置为null
    items[takeIndex] = null;
    // 2.如果takeIndex自增之后等于数组长度,说明已经取到最后一个元素了
    // 这时需要进行一次轮回,重新从0开始
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 3.很重要的一步,唤醒存放值时被沉睡的线程
    notFull.signal();
    return x;
}

    3)ArrayBlockingQueue.take()

public E take() throws InterruptedException {
    // 1.获取锁对象,可被中断
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 2.如果数组尾空,则执行等待
        // 可以与offer方法对应着看
        while (count == 0)
            notEmpty.await();
        // 3.执行dequeue方法,上面已经分析过
        return dequeue();
    } finally {
        lock.unlock();
    }
}

    总结:

    * remove 返回true或false

    * poll 如果队列为空则返回null

    * take 如果队列为空则执行等待,一直到有数据进入队列或者线程中断

5.查询操作

// ArrayBlockingQueue.peek()
public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 直接获取takeIndex的数据
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        lock.unlock();
    }
}

// ArrayBlockingQueue.itemAt()
final E itemAt(int i) {
    return (E) items[i];
}

总结:

https://blog.csdn.net/wei_ya_wen/article/details/19344939 这篇博客有一个关于这些方法的总结觉得不错,大家可以参考下

疑问:这个remove()抛出异常,实在没明白怎么抛异常的?

发布了124 篇原创文章 · 获赞 126 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/86082527