阻塞队列之ArrayBlockingQueue源码分析

BlockingQueue是一个阻塞队列,在高并发场景应用的非常广泛,最常见的情景应该就是在线程池中,其他的比如在ZooKeeper源码中,dubbo源码中也大量使用了阻塞队列,我们经常使用的也就是ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,ConcurrentLinkedQueue这几种。

ArrayBlockingQueue

ArrayBlockingQueue是一个有界队列,它本质是一个由数组组成的FIFIO的阻塞队列。它的大小是固定不可变的。新添加的元素会放入队列的尾部,获取元素的时候是从队列的头部开始遍历。当队列满了的时候,继续向队列添加元素,队列会阻塞,直到队列中有元素被取出才能继续添加。当队列为空的时候,向队列获取元素的时候,队列阻塞直到队列中添加进来了元素为止。注意,不能往队列中添加null元素。ArrayBlockingQueue使用ReentrantLock作为锁,它可以指定是否使用公平锁。默认是为false,访问顺序不确定,如果设为true,就按照FIFO的顺序访问,但这样通常会降低吞吐量。

构造方法

 /** 存放元素的object数组 */
    final Object[] items;

    /** take, poll, peek or remove的下一个索引 */
    int takeIndex;

    /** put, offer, or add的下一个索引 */
    int putIndex;

    /** 队列中元素的数量 */
    int count;

    /** 可重入锁 */
    final ReentrantLock lock;

    /** 队列不为空的condition队列 */
    private final Condition notEmpty;
   
  /** 队列不满的condition队列 */
  private final Condition notFull;
    // 默认的构造方法,设为队列的初始容量,this调用下面的构造方法
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    // fair为true or false,代表是否开始公平锁
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)  // 队列容量必须 >0,否则直接抛出异常
            throw new IllegalArgumentException();
        this.items = new Object[capacity]; // 构造一个初始容量大小的数组
        lock = new ReentrantLock(fair);   // 创建ReentrantLock,并且指定是否开启公平锁
        notEmpty = lock.newCondition();   // 创建两个condition队列
        notFull =  lock.newCondition(); 
    }

    // 传入一个集合队列
    public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);
      
        final ReentrantLock lock = this.lock;
        lock.lock(); // 加锁
        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;  // 集合为空,直接把putIndex设为0,否则,直接把putIndex设为count
        } finally {
            lock.unlock();   // 一定要释放锁
        }
    }

入队

 1.1  add

    // 添加一个元素,其实调用了offer方法进行判断,如果队列满了直接抛出异常,添加成功直接返回true
    public boolean add(E e) {
        return super.add(e);
    }
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

1.2  offer

    public boolean offer(E e) {
        checkNotNull(e);  // 元素不能为null
        final ReentrantLock lock = this.lock; 
        lock.lock();
        try {
            if (count == items.length)  // 如果队列满了,直接返回false,表示添加失败
                return false;
            else {                     // 队列未满,直接把元素插入到队列,并且直接返回true
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

1.3  put

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();  // 线程被中断后直接抛出异常,响应锁中断的方法,所以put必须要try...catch
        try {
            while (count == items.length)  // 队列满了的时候,线程要释放锁,一直等待唤醒信号
                notFull.await();
            enqueue(e);   // 等待唤醒信号竞争到锁之后,说明队列已经不是已满的状态,再进行入队
        } finally {
            lock.unlock();
        }
    }

1.4  offer

    /**
     * 
     * 在指定时间内入队
     */
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);  // 将时间转化为纳秒
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();   // 指定时间的入队使用了响应锁中断
        try {
            while (count == items.length) { // 队列满了,如果nanos <= 0,已超时,直接返回false
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);  // 进行等待,在此过程中线程可能被唤醒
            }
            enqueue(e);  // 线程获取唤醒信号后竞争到锁,直接返回true
            return true;
        } 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;   // 将元素放入putIndexu索引处
        if (++putIndex == items.length)  // 如果putIndex索引+1 == 队列的容量,直接把putIndex索引设为0
            putIndex = 0;
        count++;    // 元素个数自增
        notEmpty.signal();  // 唤醒notFull队列上等待的线程
    }

出队

 2.1  poll

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();   
        try {      // 如果队列为空直接返回null,否则调用dequeue方法
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

2.2  take

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();   // take方法使用的是响应锁中断,线程被中断直接抛异常
        try {
            while (count == 0)   // 当队列为空时线程释放锁,一致等待唤醒信号
                notEmpty.await();
            return dequeue();    // 出队操作
        } finally {
            lock.unlock();
        }
    }

2.3  poll

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();  // 指定时间的出队使用了响应锁中断
        try {
            while (count == 0) {   // 当队列为空时,如果nanos <=0,已超时或者传入的参数<0,直接返回null
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);  // 在nanos时间内等待唤醒信号
            }
            return dequeue();   // 队列不为空或者在nanos时间内队列添加进元素线程通过唤醒信号竞争到锁直接出队操作
        } finally {
            lock.unlock();
        }
    }

看一下dequeue的操作

    private E dequeue() {  
        final Object[] items = this.items;  
        @SuppressWarnings("unchecked")  
        E x = (E) items[takeIndex];  // takeIndex初始化为0,所以第一次获取元素的时候,总是返回数组的第一个元素,正好是FIFO
        items[takeIndex] = null;  // 把takeIndex位置上的元素置为空,便于GC
        if (++takeIndex == items.length)  // 如果takeIndex+1 == 队列容量,说明队列满了,直接把takeIndex置为0
            takeIndex = 0;  
        count--;   // 队列元素个数自减
        if (itrs != null)   // jdk 1.8 新增加的特性 迭代器
            itrs.elementDequeued();  
        notFull.signal();   // 能够取出元素,说明队列不为空,唤醒notEmpty队列上等待的线程
        return x;   // 直接返回takeIndex索引上的元素
    }  

2.4  peek

    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {  // 队列为空直接返回null,否则调用itemAt方法返回当前队列takeIndex上的元素,即当前队列最前面的元素
            return (count == 0) ? null : itemAt(takeIndex);
        } finally {
            lock.unlock();
        }
    }
    @SuppressWarnings("unchecked")
    static <E> E cast(Object item) {
        return (E) item;
    }

    /**
     * 返回takeIndex上的元素
     */
    final E itemAt(int i) {
        return this.<E>cast(items[i]);
    }

删除

/** 
 *  在队列中删除一个给定的元素 
 *  如果要删除的元素为null,直接返回false 
 *  如果count <= 0,直接返回false
 *  当count>0,从takeIndex开始调用equal方法比较遍历,当元素相同的时候,调用removeat方法删除元素,返回true
 *  ++i == 队列容量,直接把i置为0,如果i=putIndex,说明在队列中未查找到要删除的元素,直接返回false
 */  
public boolean remove(Object o) {
       if (o == null) return false;
       final Object[] items = this.items;
       final ReentrantLock lock = this.lock;
       lock.lock();
       try {
           if (count > 0) {
               final int putIndex = this.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 {
           lock.unlock();
       }
   }

总结

我们最常用的也就是它的入队和出队方法。 

入队:add   队列未满添加元素返回true,队列满了直接抛出异常  

          put    队列满了一直阻塞直到队列有空间才进行添加元素

          offer  队列未满直接添加元素返回true,队列满了直接返回false

 出队:poll   队列为空直接返回null,队列不为空直接返回队首元素

           take   队列为空一直等待直到队列中有元素,直接返回队首元素

ArrayBlockingQueue使用ReentrantLock来保证并发时的线程安全,它是基于数组和两个condition队列来实现的。总体来说,它的实现思想还是比较简单的,应该是BlockingQueue中最好理解的一个类。









猜你喜欢

转载自blog.csdn.net/qq_30572275/article/details/80399187