Java阻塞队列ArrayBlockingQueue使用及原理分析

什么是阻塞队列?

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

方法 抛出异常 返回特殊值 一直阻塞 超时退出
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用
  • 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
  • 返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
  • 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
  • 超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。

核心成员变量

    // ArrayBlockingQueue队列底层用数组存储维护
    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;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

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

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

    /** Condition for waiting puts */
    private final Condition notFull;

Constructor 构造函数

// 外部指定容量创建队列,默认是非公平锁
public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
}

// 外部指定容量、公平锁创建队列
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();
}

入队

  • offer。成功返回true,失败返回false
public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        // 获取独占锁
        lock.lock();
        try {
            // 队列已满,直接返回false
            if (count == items.length)
                return false;
            else {
            // 元素入队
                enqueue(e);
                return true;
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
}
  • put : 队列满,会阻塞调用存储元素的线程
public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await(); // 队列已满,阻塞调用存储的线程
            enqueue(e);
        } 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();
}
  • poll :获取元素,存在返回元素e,不存在返回null
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
  • take : 取元素。如果队列为空,则会阻塞调用获取元素的线程。
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
  • dequeue : 出队操作
private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        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;
}

写在最后

源码分析看的是JDK8源码。

你可能感兴趣的:(tool,java)