什么是 LinkedBlockingQueue
?
LinkedBlockingQueue
是 Java 中一个线程安全的 阻塞队列,它位于 java.util.concurrent
包中,底层使用 链表 实现。它被广泛用于生产者-消费者模型中,可以在多线程环境下安全地传递和管理数据。
它的核心特点是:
- 容量限制:可以指定容量限制,防止无限制增长。
- 线程安全:通过内置的锁机制(分离读写锁),实现线程安全。
- 阻塞行为:在队列满时生产者线程阻塞,在队列空时消费者线程阻塞。
关键特性
-
基于链表实现:
- 队列的元素通过链表节点存储,动态扩展,避免了数组实现中的容量问题。
-
容量控制:
- 在构造时可以指定队列的最大容量。如果不指定,默认容量为
Integer.MAX_VALUE
。
- 在构造时可以指定队列的最大容量。如果不指定,默认容量为
-
线程安全:
- 使用了两把独立的锁:
putLock
(用于插入操作)和takeLock
(用于移除操作)。读写操作可以并发执行,提升性能。
- 使用了两把独立的锁:
-
阻塞操作:
- 提供阻塞的
put
和take
方法:put
:如果队列满,当前线程会阻塞直到有空余空间。take
:如果队列为空,当前线程会阻塞直到队列有元素。
- 提供阻塞的
-
非阻塞操作:
- 提供非阻塞的
offer
和poll
方法,可以指定等待时间:offer
:尝试插入元素,若队列满则返回false
。poll
:尝试移除元素,若队列空则返回null
。
- 提供非阻塞的
主要方法
方法名 | 描述 |
---|---|
put(E e) | 阻塞地将元素放入队列,如果队列满则等待空间。 |
take() | 阻塞地从队列取出元素,如果队列空则等待元素。 |
offer(E e) | 尝试将元素放入队列,若队列满则返回 false 。 |
offer(E e, long timeout, TimeUnit unit) | 尝试在指定时间内将元素放入队列,如果超时则返回 false 。 |
poll() | 尝试从队列取出元素,若队列空则返回 null 。 |
poll(long timeout, TimeUnit unit) | 尝试在指定时间内取出元素,如果超时则返回 null 。 |
peek() | 查看队列头部元素,但不移除,若队列为空返回 null 。 |
size() | 返回队列中当前的元素个数。 |
remainingCapacity() | 返回队列的剩余空间(容量 - 当前大小 )。 |
drainTo(Collection<? super E> c) | 将队列中所有元素转移到另一个集合中,并清空队列。 |
核心原理
1. 链表结构
LinkedBlockingQueue
的底层是一个链表,其节点由静态内部类 Node
定义,每个节点存储一个元素。链表的结构如下:
2. 锁分离
LinkedBlockingQueue
使用了两个独立的锁 putLock
和 takeLock
:
putLock
:控制插入操作。takeLock
:控制取出操作。- 锁分离允许插入和取出操作可以同时进行,提升并发性能。
此外,还使用了两个条件变量 notEmpty
和 notFull
来实现阻塞机制:
notEmpty
:当队列不为空时通知等待的消费者线程。notFull
:当队列未满时通知等待的生产者线程。
3. 容量限制
LinkedBlockingQueue
的容量可以在创建时指定。默认容量是Integer.MAX_VALUE
。- 如果队列已满,生产者线程会阻塞直到有空余空间。
- 如果队列为空,消费者线程会阻塞直到有新的元素被插入。
代码示例
基本使用
import java.util.concurrent.LinkedBlockingQueue;public class LinkedBlockingQueueExample {public static void main(String[] args) throws InterruptedException {// 创建一个容量为3的阻塞队列LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(3);// 向队列中插入元素queue.put(1);queue.put(2);queue.put(3);// 尝试插入元素(阻塞,因为队列已满)new Thread(() -> {try {System.out.println("Trying to add element...");queue.put(4);System.out.println("Element added!");} catch (InterruptedException e) {e.printStackTrace();}}).start();// 取出元素,释放空间Thread.sleep(2000);System.out.println("Taking element: " + queue.take());}
}
生产者-消费者模型
import java.util.concurrent.LinkedBlockingQueue;public class ProducerConsumer {public static void main(String[] args) {LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);// 生产者线程Thread producer = new Thread(() -> {try {for (int i = 1; i <= 10; i++) {System.out.println("Produced: " + i);queue.put(i); // 阻塞地放入队列}} catch (InterruptedException e) {e.printStackTrace();}});// 消费者线程Thread consumer = new Thread(() -> {try {while (true) {Integer item = queue.take(); // 阻塞地取出队列System.out.println("Consumed: " + item);}} catch (InterruptedException e) {e.printStackTrace();}});producer.start();consumer.start();}
}
应用场景
-
生产者-消费者模型:
- 生产者将任务放入队列,消费者从队列中取出任务进行处理。
- 通过阻塞机制自动协调生产和消费的速度。
-
多线程通信:
- 在线程间传递数据,避免显式的同步操作。
-
任务调度:
- 用于线程池的工作队列,存储需要执行的任务。
优缺点
优点
- 线程安全: 内置锁机制,读写操作独立。
- 容量控制: 可以防止内存占用过多。
- 阻塞功能: 自动协调生产和消费速度,无需手动管理同步。
缺点
- 性能较低: 相比非阻塞队列,锁机制可能带来一定的性能开销。
- 扩展性有限: 无法灵活调整容量限制。
LinkedBlockingQueue 底层实现
关于阻塞机制-CSDN博客