W
e
l
c
o
m
e
: )

并发编程之阻塞队列

一、基础概念

1.1 生产者消费者

生产者消费者其实是一种设计模式,它是为了解决生产者和消费者之间的强耦合问题,为了解决这个问题,我们通常会选择引入一个中间容器(队列),生产者不直接和消费者打交道,它会把消息扔到中间容器中,而消费者需要自己去中间容器内拿到消息进行处理。像我们在项目中常使用的RocketMQ、RabbitMQ就是基于生产者消费者来实现的。

1.2 JUC阻塞队列的存取方法

image.png

常用的存储方法:

add(E)	//往队列中添加数据,如果队列满了,抛出异常
offer(E) //往队列中添加数据,如果队列满了,返回false
offer(E,timeout,unit) //往队列中添加数据,如果队列满了,等待timeout,超过时间还没放进去,返回false
put(E) //往队列中添加数据,如果队列满了,挂起线程,等到有位置放进去

常用的获取方法:

remove() //从队列中获取数据,如果队列为空,抛出异常
poll() //从队列中获取数据,如果队列为空,返回null
poll(timeout,unit) //从队列中获取数据,如果队列为空,等待timeout,超过时间还是没有,返回null
take() //从队列中获取数据,如果队列为空,挂起等待,等到有数据获取

二、ArrayBlockingQueue

2.1 基础使用

ArrayBlockingQueue是基于数组实现的队列,所以我们在创建实例的时候必须指定数组的长度。

public class Code01_ArrayBlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        //必须指定数组的长度
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(4);
        //生产者生产数据
        queue.add(1);
        queue.offer(2);
        queue.offer(3,2, TimeUnit.SECONDS);
        queue.put(4);
        //消费者消费数据
        System.out.println(queue.remove());
        System.out.println(queue.poll());
        System.out.println(queue.poll(2, TimeUnit.SECONDS));
        System.out.println(queue.take());
    }
}

2.2 核心成员变量

//存储元素的队列
final Object[] items;
//消费者获取元素的下标
int takeIndex;
//生产者存储元素的下标
int putIndex;
//队列中存储的元素个数
int count;
//阻塞队列用于并发控制的锁
final ReentrantLock lock;
//用于存放获取不到数据而挂起的消费者
private final Condition notEmpty;
//用于存放存储不进数据而挂起的生产者
private final Condition notFull;

2.3 生产者方法分析

2.3.1 add方法

public boolean add(E e) {
    return super.add(e);
}

public boolean add(E e) {
    //这里调用的还是ArrayBlockingQueue里面的offer方法
    if (offer(e))
        //添加成功,返回true
        return true;
    else
        //添加失败,抛异常
        throw new IllegalStateException("Queue full");
}

2.3.2 offer方法

public boolean offer(E e) {
    //检查是否为空
    checkNotNull(e);
    //拿到队列的lock锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            //如果队列已经满了,返回false
            return false;
        else {
            //如果没满,入队
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
    //拿到队列
    final Object[] items = this.items;
    //将元素放进队列
    items[putIndex] = x;
    if (++putIndex == items.length)
        //如果++putIndex等于队列长度,表示队列满了,putIndex归0
        putIndex = 0;
    //元素数量++
    count++;
    //唤醒在Condition队列中等待的消费者
    notEmpty.signal();
}

2.3.3 offer(timeout,unit)

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    //检查元素是否为空
    checkNotNull(e);
    //将等待时间转成纳秒
    long nanos = unit.toNanos(timeout);
    //拿到队列的lock锁
    final ReentrantLock lock = this.lock;
    //加锁,可中断
    lock.lockInterruptibly();
    try {
        //循环判断队列是否是满的状态
        while (count == items.length) {
            if (nanos <= 0)
                //如果队列已满,并且等待时间不够了,返回false
                return false;
            //挂起当前线程指定时间到notFull对应的Condition队列,返回的nanos是剩余时间(被唤醒了)
            nanos = notFull.awaitNanos(nanos);
        }
        //队列未满,入队
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

2.3.4 put方法

public void put(E e) throws InterruptedException {
    //检查是否为空
    checkNotNull(e);
    //拿到当前队列的lock锁
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            //如果队列已满,就挂起等待唤醒
            notFull.await();
        //队列未满,入队
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

2.4 消费者方法分析

2.4.1 remove方法
public E remove() {
    //调用poll方法难道队列元素
    E x = poll();
    if (x != null)
        //不为空返回元素
        return x;
    else
        //为空则抛出异常
        throw new NoSuchElementException();
}

2.4.2 poll方法

public E poll() {
    //拿到队列的lock锁
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
        //如果队列为空,返回null,否则出队
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

private E dequeue() {
    //拿到队列
    final Object[] items = this.items;
    //从队列中取出takeIndex下标对应元素
    E x = (E) items[takeIndex];
    //将该位置元素置为空
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        //如果队列元素拿光了,takeIndex归0
        takeIndex = 0;
    //元素数量--
    count--;
    //itrs暂且不看。。。
    if (itrs != null)
        itrs.elementDequeued();
    //唤醒notFull里面挂起的生产者
    notFull.signal();
    return x;
}

2.4.3 poll(timeout,unit)

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    //将等待时间转纳秒
    long nanos = unit.toNanos(timeout);
    //拿到队列的lock锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0) {
            if (nanos <= 0)
                //如果剩余等待时间不足,返回null
                return null;
            //队列没数据,挂起当前消费者等待唤醒
            nanos = notEmpty.awaitNanos(nanos);
        }
        //出队
        return dequeue();
    } finally {
        lock.unlock();
    }
}

2.4.4 take方法

public E take() throws InterruptedException {
    //拿到队列的lock锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            //没数据就挂起
            notEmpty.await();
        //有数据执行出队
        return dequeue();
    } finally {
        lock.unlock();
    }
}

2.5 虚假唤醒问题

先看下面两段代码:

//逻辑1
if(count == 0){
	notEmpty.await();
}
return dequeue();
//逻辑2
while(count == 0){
	notEmpty.await();
}
return dequeue();

假设现在有一个消费者线程A来队列中拿数据,但此时队列为空,它只能选择挂起等待被唤醒;接着来了一个生产者线程B,它拿到lock锁,向队列中存放数据,然后将正在等待的A线程唤醒了,于是线程A准备去拿锁了,但是好巧不巧,刚好有个消费者线程C先它一步拿到了lock锁,于是它把刚刚线程B存的数据拿走了,接着A拿到了锁,如果是执行的逻辑1的代码,那么它就会执行出队操作,但此时队列的数据已经被拿走了,队列是空的,这就是虚假唤醒。我们前面不管是往队列中存还是取数据,都会加上while循环而不是if判断,就是为了避免出现虚假唤醒问题。

三、LinkedBlockingQueue

3.1 底层实现

LinkedBlockingQueue底层是基于链表结构实现的,链表节点封装为一个Node对象存储。

//LinkedBlockingQueue存储数据的载体
static class Node<E> {
    E item;//数据
    Node<E> next;//下一个节点

    Node(E x) { item = x; }
}

3.2 构造方法与核心成员变量

//无参构造
public LinkedBlockingQueue() {
    //默认链表最大长度为Integer.MAX_VALUE
    this(Integer.MAX_VALUE);
}
//指定长度的构造
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    //链表的head头节点和last尾节点是两个伪节点
    last = head = new Node<E>(null);
}

//链表最大长度
private final int capacity;

//当前链表长度,即当前有多少个链表节点
private final AtomicInteger count = new AtomicInteger();

//head头节点,用于获取数据
transient Node<E> head;

//last尾节点,用于存放数据
private transient Node<E> last;

//获取数据的lock锁
private final ReentrantLock takeLock = new ReentrantLock();

//获取数据时链表为空,挂起线程的Condition
private final Condition notEmpty = takeLock.newCondition();

//存放数据的lock锁
private final ReentrantLock putLock = new ReentrantLock();

//存放数据时链表已满,挂起线程的Condition
private final Condition notFull = putLock.newCondition();

3.3 生产者方法分析

3.3.1 add方法

public boolean add(E e) {
    //调用的offer方法
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

3.3.2 offer方法

public boolean offer(E e) {
    //如果添加数据为空,抛异常
    if (e == null) throw new NullPointerException();
    //拿到当前链表节点个数
    final AtomicInteger count = this.count;
    //如果当前链表节点个数等于capacity,说明满了,返回false
    if (count.get() == capacity)
        return false;
    int c = -1;
    //将数据封装为Node对象
    Node<E> node = new Node<E>(e);
    //拿到putLock锁
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        //判断当前节点个数有没有超出限制,前面判断的时候还没有拿到锁
        //这个和synchronized那个双重检验锁很像
        if (count.get() < capacity) {
            //执行入队操作
            enqueue(node);
            //注意这里是先get再++,也就是说c获得的是count的旧值
            c = count.getAndIncrement();
            //其实就是count是否小于capacity,如果是,就唤醒其它的生产者,告诉它们有空位可以存数据
            //为什么这里不像ArrayBlockingQueue一样让消费者来唤醒而是要自唤醒呢,因为LinkedBlockingQueue
            //的读锁和写锁是分离的,消费者想要唤醒生产者必须要拿到写锁才行,而我们这里已经是持有锁状态了
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        //c为0表示之前队列是空的,有可能有消费者线程正在挂起,我们需要把它们唤醒
        signalNotEmpty();
    return c >= 0;
}

private void enqueue(Node<E> node) {
    //先将last.next指向当前node,然后再将last指向node
    last = last.next = node;
}

private void signalNotEmpty() {
    //拿到读锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        //唤醒正在挂起的消费者
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

3.3.3 offer(timeout,unit)

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    //如果元素为空抛异常
    if (e == null) throw new NullPointerException();
    //等待时间转纳秒
    long nanos = unit.toNanos(timeout);
    int c = -1;
    //拿到写锁和count
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {//是否已满
            if (nanos <= 0)
                return false;
            //挂起线程
            nanos = notFull.awaitNanos(nanos);
        }
        //入队
        enqueue(new Node<E>(e));
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            //还有位置,唤醒其它生产者
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        //唤醒消费者
        signalNotEmpty();
    return true;
}

3.3.4 put方法

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

3.4 消费者方法分析

3.4.1 remove方法

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

3.4.2 poll方法

public E poll() {
    //拿到count
    final AtomicInteger count = this.count;
    if (count.get() == 0)
        //没数据,返回null
        return null;
    E x = null;
    int c = -1;
    //拿到读锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() > 0) {
            //如果count>0,才能执行出队操作
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                //还有数据呢,唤醒其它消费者来拿
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        //消费者把数据拿了,唤醒生产者继续生产
        signalNotFull();
    return x;
}

private E dequeue() {
    Node<E> h = head;
    //拿到head的下一个节点
    Node<E> first = h.next;
    //头节点next指向自己,没有node指向自己,也不指向别的节点,帮助GC
    h.next = h; // help GC
    //head指向first,原来的head被GC了,first成为新的head
    head = first;
    //拿到first里面的数据
    E x = first.item;
    //清空数据
    first.item = null;
    //返回
    return x;
}

3.4.3 poll(timeout,unit)

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
  
    E x = null;
    int c = -1;
    long nanos = unit.toNanos(timeout);
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

3.4.4 take方法

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}
posted @ 2026-06-08 18:32  寒月静无光  阅读(3)  评论(0)    收藏  举报