并发集合类(3):LinkedBlockingQueue

什么是LinkedBlockingQueue?

LinkedBlockingQueueJUC中基于独占锁实现的阻塞队列。它本身底层是由链表实现的,内部使用ReentrantLock来作为锁。

不同于ConcurrentLinkedQueueLinkedBlockingQueue在队列满时,put()方法会阻塞,队列为空的时候,take()方法会阻塞。

一般使用LinkedBlockingQueue来实现生产者-消费者模型,来解耦线程,也可以作为线程池的工作队列。

LinkedBlockingQueue的内部结构

内部节点

java 复制代码
static class Node<E> {
    E item;

    /**
     * One of:
     * - the real successor Node
     * - this Node, meaning the successor is head.next
     * - null, meaning there is no successor (this is the last node)
     */
    Node<E> next;

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

这是一个典型的单向链表节点。 其中的next有三种情况:

  1. 当该节点仍在队列中时,next指向下一个有效节点
  2. next指向自身,代表该节点已经出队。队列下一个要出队的节点应该是head.next
  3. nextnull,代表这个节点是队列中的最后一个节点

因此节点的next不仅仅表示链表关系,并且承载了节点生命周期状态

内部数据域

java 复制代码
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;

/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();

/**
 * Head of linked list.
 * Invariant: head.item == null
 */
transient Node<E> head;

/**
 * Tail of linked list.
 * Invariant: last.next == null
 */
private transient Node<E> last;

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notFull = putLock.newCondition();
  1. capacity用于指定队列的最大容量。
  2. count用于确定当前队列中的元素个数,使用AtomicInteger是因为入队和出队使用的锁不是同一把,无法用一把锁来保护count
  3. head是哨兵节点,last是尾节点
  4. takeLock用于出队的同步控制,putLock用于入队的同步控制。
  5. 条件变量notEmpty当队列为空的时候,出队线程阻塞,条件变量notFull,当队列满时,入队线程等待。

构造方法

java 复制代码
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

在无参构造的情况下,队列的大小是Integer.MAX_VALUE

LinkedBlockingQueue的操作

offer方法

java 复制代码
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false;
    final int c;
    final Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        if (count.get() == capacity)
            return false;
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}

offer方法是非阻塞方法,如果队列未满,则入队成功,返回true,否则入队失败,返回false

  1. 先预检count,如果队列已满,可以减少不必要的加锁流程。
  2. 获取putLock,并再次检查容量
  3. 节点入队
  4. 容量原子性增加,并且如果入队后仍然未满,唤醒一个put线程
  5. 如果原先容量为0,唤醒take线程

注意我们说offer是非阻塞方法,是指它不会阻塞在条件队列中,但是可能在竞争锁时阻塞在同步队列中。

put方法

java 复制代码
/**
 * Inserts the specified element at the tail of this queue, waiting if
 * necessary for space to become available.
 *
 * @throws InterruptedException {@inheritDoc}
 * @throws NullPointerException {@inheritDoc}
 */
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    final int c;
    final Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        /*
         * Note that count is used in wait guard even though it is
         * not protected by lock. This works because count can
         * only decrease at this point (all other puts are shut
         * out by lock), and we (or some other waiting put) are
         * signalled if it ever changes from capacity. Similarly
         * for all other uses of count in other wait guards.
         */
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

put方法是一个阻塞方法,并且可被中断。

  1. 加可中断的锁putLock.lockInterruptibly()
  2. 当队列满时,阻塞直到被唤醒
  3. 节点入队
  4. 如果入队之后还有空余,唤醒其他put线程
  5. 如果入队前,队列为空,唤醒其他take线程

poll方法

java 复制代码
public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0)
        return null;
    final E x;
    final int c;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() == 0)
            return null;
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

poll是非阻塞式出队。

  1. 同样是不加锁检查容量,当容量为0时快速失败
  2. 获取takeLock锁,并再次检查容量是否为0
  3. 元素出队
  4. 容量原子性减少,如果容量大于0,唤醒其他take线程
  5. 当容量之前是满时,唤醒put线程

take方法

java 复制代码
public E take() throws InterruptedException {
    final E x;
    final int c;
    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;
}

take是阻塞式取元素方法。

  1. 可中断式获取锁。
  2. 当容量为0时阻塞
  3. 元素出队
  4. 容量原子性递减
  5. 如果容量有剩余,唤醒其他take线程
  6. 如果容量之前是满的,唤醒其他put线程
相关推荐
李温候1 小时前
互联网大厂Java求职者面试全攻略
java·数据库·面试·orm·构建工具·web框架·互联网大厂
Apifox1 小时前
Apifox 近期更新|AI Agent Debugger、A2A Debugger、Postman API 导入、Ask AI 侧边栏对话
前端·人工智能·后端
摇滚侠1 小时前
软件开发外包项目组,如何提高代码质量和开发效率
java·开发语言·前端·ide·intellij-idea
知识浅谈1 小时前
面向方面编程(AOP)VS 面向对象编程(OOP)
后端
bzmK1DTbd2 小时前
MongoDB聚合框架:Java驱动下的数据聚合操作
java·python·mongodb
IT空门:门主2 小时前
spring ai alibaba -流式+invoke的人工介入的实现
java·后端·spring
TE-茶叶蛋2 小时前
mvn test
java
fliter2 小时前
4 个字节拿到 root 权限:Linux 内核漏洞"Copy Fail"与 Cloudflare 的应急处置全记录
后端