Java并发编程(十一)阻塞队列ArrayBlockingQueue

67 阅读3分钟

阻塞队列:

  • 队列,先进先出的一个数据结构
  • 阻塞,基于ReentrantLock实现的,并且线程的挂起也是通过Condition

ArrayBlockingQueue底层是采用数组实现的一个队列。因为使用的是数组,又被称为有界队列。

1 ArrayBlockingQueue应用

存数据操作 add(E),offer(E),put(E),offer(E,time,unit)

  • add(E):添加数据到队列,如果满了,抛异常。
  • offer(E):添加数据到队列,如果满了,返回false
  • put(E):添加数据到队列,如果满了,线程挂起
  • offer(E,time,unit):添加数据到队列,如果满了,线程挂起一段时间

取数据操作 remove(),poll(),take(),poll(time,unit)

  • remove():从队列拿数据,拿到返回,拿到null,抛异常
  • poll():从队列拿数据,拿到返回,拿到null,也返回
  • take():从队列拿数据,拿到返回,没数据,一直阻塞
  • poll(time,unit):从队列拿数据,拿到返回,没数据,阻塞time时间

2 ArrayBlockingQueue存取源码分析

2.1 存数据

  • enqueue,数据存入数组的方法,供其他方法调用
// 存放数据到数组中
private void enqueue(E x) {
    // 拿到数组
    final Object[] items = this.items;
    // 数组放进去
    items[putIndex] = x;
    // 把put指针++, 指针是否已经到了最后一个位置,归位到0位置。
    if (++putIndex == items.length)
        // 归位到0位置。
        putIndex = 0;
    // 数据条数 + 1
    count++;
    // 唤醒在阻塞的取数据线程
    notEmpty.signal();
}

  • offer,添加时,先判断队列满了没,满了就返回false
// 存数据
public boolean offer(E e) {
    // 非空校验
    checkNotNull(e);
    // 互斥锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 如果数组中的数据已经达到了数组的长度,队列满了
        if (count == items.length)
            return false;
        else {
            // 还有位置
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
  • offer(time,unit),添加时,先判断队列满了没,满了先阻塞time时间,自动唤醒,还是满的,也返回false
// 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) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}
  • put,添加时,先判断队列满了没,满了就阻塞,阻塞到被唤醒,或者被中断
// 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();
    }
}

2.2 取数据

  • dequeue,从数组中拿出数据的实际方法
private E dequeue() {
    final Object[] items = this.items;
    // 取数据
    E x = (E) items[takeIndex];
    // 将取完的位置置位null
    items[takeIndex] = null;
    // take指针++,如果到头,归位0~~
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 数据条数 - 1
    count--;
    // 唤醒队列满的时候,阻塞住的写线程
    notFull.signal();
    return x;
}
  • poll,取数据,队列为空时返回null
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // count == 0代表没数据, 就返回null,有数据走dequeue
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}
  • poll(long timeout, TimeUnit unit),取数据,队列为空时阻塞,超时后返回null
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) {
            if (nanos <= 0)
                return null;  
            nanos = notEmpty.awaitNanos(nanos);   //  挂起线程,到时间自动唤醒、或者被手动唤醒
        }
        return 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();
    }
}