阻塞队列(BlockingQueue
)是java.util.concurrent
包中的一种并发容器,它支持线程安全的阻塞操作,即在队列为空时获取元素会阻塞线程,直到有元素可以获取;在队列满时插入元素也会阻塞,直到队列有空间为止。阻塞队列非常适合在多线程环境下用于实现生产者-消费者模型。
阻塞队列的特性
- 线程安全:阻塞队列在插入、删除等操作时内部使用了锁机制,保证多线程环境下的线程安全性。
- 阻塞操作:
- 如果队列为空,获取元素时会阻塞,直到有元素可获取。
- 如果队列已满,插入元素时会阻塞,直到队列有空位。
- 多种操作方法:
- 抛异常方法:如
add()
、remove()
、element()
等,在操作失败时抛出异常。 - 返回特殊值的方法:如
offer()
、poll()
、peek()
等,操作失败时返回特定值,如null
或false
。 - 阻塞方法:如
put()
、take()
,操作失败时阻塞线程直到成功。 - 超时方法:如
offer(e, time, unit)
、poll(time, unit)
,操作失败时等待一段时间,超时后返回false
或null
。
- 抛异常方法:如
常见阻塞队列的实现类
BlockingQueue
有多种实现,适用于不同场景:
1. ArrayBlockingQueue
- 特点:基于数组实现的有界阻塞队列,必须指定容量大小。
- 线程安全性:通过内部锁机制确保线程安全。
- 使用场景:常用于生产者-消费者模式中限制资源数量的场景。
示例:
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3); // 容量为3的队列
// 生产者线程
new Thread(() -> {
try {
queue.put(1);
System.out.println("生产者放入: 1");
queue.put(2);
System.out.println("生产者放入: 2");
queue.put(3);
System.out.println("生产者放入: 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
Thread.sleep(1000); // 模拟延迟
System.out.println("消费者取出: " + queue.take());
System.out.println("消费者取出: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
2. LinkedBlockingQueue
- 特点:基于链表实现,可以指定容量大小,如果不指定则为无界队列。
- 线程安全性:通过内部锁机制确保线程安全。
- 使用场景:适合高并发场景下的大量数据交换,可以避免由于容量限制导致的过多阻塞。
示例:
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(); // 默认无界队列
// 生产者线程
new Thread(() -> {
try {
queue.put(1);
System.out.println("生产者放入: 1");
queue.put(2);
System.out.println("生产者放入: 2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
System.out.println("消费者取出: " + queue.take());
System.out.println("消费者取出: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
3. PriorityBlockingQueue
- 特点:基于优先级的无界阻塞队列,元素会根据自然顺序或提供的
Comparator
排序。没有容量限制,内部使用二叉堆实现。 - 线程安全性:通过内部锁机制确保线程安全。
- 使用场景:适用于需要优先级处理任务的场景,例如任务调度。
示例:
BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
// 生产者线程
new Thread(() -> {
try {
queue.put(5);
System.out.println("生产者放入: 5");
queue.put(1);
System.out.println("生产者放入: 1");
queue.put(3);
System.out.println("生产者放入: 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
System.out.println("消费者取出: " + queue.take()); // 最先取出优先级高的元素
System.out.println("消费者取出: " + queue.take());
System.out.println("消费者取出: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
4. SynchronousQueue
- 特点:不存储元素的阻塞队列,每个插入操作必须等待一个相应的移除操作,反之亦然。是一个"直接交换"的队列。
- 线程安全性:通过内部锁机制确保线程安全。
- 使用场景:适用于传递性场景,常用于生产者-消费者模型中一个生产者一个消费者的情况。
示例:
BlockingQueue<String> queue = new SynchronousQueue<>();
// 生产者线程
new Thread(() -> {
try {
queue.put("消息1");
System.out.println("生产者发送: 消息1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
System.out.println("消费者接收: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
5. DelayQueue
- 特点:实现了
Delayed
接口的元素可以按指定的延迟时间存储,只有当元素的延迟时间到达后,才能从队列中获取。常用于定时任务调度。 - 线程安全性:通过内部锁机制确保线程安全。
- 使用场景:适用于延迟任务执行的场景,如任务调度、消息推送等。
示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
class DelayedElement implements Delayed {
private long delayTime;
private String message;
public DelayedElement(String message, long delayTime) {
this.message = message;
this.delayTime = System.currentTimeMillis() + delayTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return message;
}
}
public class DelayQueueExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<DelayedElement> queue = new DelayQueue<>();
queue.put(new DelayedElement("延迟消息1", 5000)); // 延迟5秒
queue.put(new DelayedElement("延迟消息2", 10000)); // 延迟10秒
while (!queue.isEmpty()) {
DelayedElement element = queue.take(); // 按延迟时间依次取出
System.out.println("取出消息: " + element);
}
}
}
总结
阻塞队列在并发编程中发挥了重要作用,尤其适合生产者-消费者模型。根据不同的需求场景,可以选择合适的阻塞队列实现来处理并发任务,例如:
ArrayBlockingQueue
:适合需要有限资源的场景。LinkedBlockingQueue
:适合大规模并发的数据交换。PriorityBlockingQueue
:适合需要优先级处理的任务。SynchronousQueue
:适合直接传递任务的场景。DelayQueue
:适合延时任务调度。
合理使用阻塞队列可以极大地简化并发程序的实现,提升系统性能和安全性。