引言
阻塞队列在数据结构中所起的作用大致如下图所示:
当阻塞队列为空时,从队列中获取元素的操作会被阻塞,直到其他线程往空队列添加新的元素。
当阻塞队列满时,往阻塞队列添加元素会被阻塞,直到其他线程从队列中移除一个元素。
为什么需要阻塞线程
在多线程领域:某些情况下会挂起线程(阻塞wait/await),一旦条件满足,被挂起的线程又会自动唤醒(notify/signal)。
使用阻塞队列的好处就是:不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,这一切BlockingQueue都一手包办了。
在Concurrent包发布前的多线程环境下,程序员需要自己去控制线程阻塞和唤醒的细节,尤其还要兼顾效率和线程安全,这势必会增加程序的复杂度。阻塞队列使编程更简单且降低了程序的复杂度。
BlockingQueue的实现及核心方法
查看类继承关系:BlockingQueue接口实现了Queue接口,Queue接口继承自Collection接口。BlockingQueue的实现类主要有以下7种:
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:由链表结构组成的有界阻塞队列(默认值为Integer.MAX_VALUE —即2147483647,使用时一定手动指定值)
SynchronousQueue:不存储元素的阻塞队列,即单个元素的队列(每一个put操作必须等待一个take操作,否则不能继续添加元素,反之亦然。)。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
- 抛出异常:
阻塞队列满时插在往队列add抛IllegalStateException:Queue full.
阻塞队列空时从队列remove元素抛NoSuchElementException. - 特殊值:插入成功返回true失败false,移除元素成功返回移除的元素,失败返回null。
- 阻塞:
阻塞队列满时,生产线程往队列put元素,队列会一直阻塞生产线程直到put数据或相应中断。
阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费线程直到队列可用。 - 超时:当阻塞队列满时,队列会阻塞生产者线程一定时间,超时候生产者线程会退出。
应用场景
阻塞队列主要的应用场景是:生产者消/费者模式、线程池及消息中间件,接下来分别用线程间通信及阻塞队列实现生产者/消费者模式。
线程间通信之生产者/消费者模式
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//生产者对变量加1操作,消费者对变量减1操作
public class ProduceConsumer {
//number大于5时停止生产
private int number = 0;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void produce() {
lock.lock();
try {
//while防止虚假唤醒
while(number >= 5) {
condition.await();
}
number++; //生产者执行++操作
System.out.println(Thread.currentThread().getName() + " number: " + number);
condition.signalAll(); //唤醒其他线程
}catch(Exception e) {
System.out.println(Thread.currentThread().getName() + " produce failed.");
}finally {
lock.unlock();
}
}
public void consumer() {
lock.lock();
try {
while(number == 0) { //number为0时消费者线程阻塞
condition.await();
}
number--; //消费者执行--操作
System.out.println(Thread.currentThread().getName() + " number: " + number);
condition.signalAll(); //唤醒其他线程
}catch(Exception e) {
System.out.println(Thread.currentThread().getName() + " consumer failed.");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ProduceConsumer produceConsumer = new ProduceConsumer();
//创建两个生产者线程
for (int i = 0; i < 2; i++) {
final int threadFlag = i;
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
produceConsumer.produce();
}
}
},"produce" + threadFlag).start();
}
//创建两个消费者线程
for (int i = 0; i < 2; i++) {
final int threadFlag = i;
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
produceConsumer.consumer();
}
}
},"consumer" + threadFlag).start();
}
}
}
阻塞队列之生产者/消费者模式:不需要关心什么时候阻塞或唤醒线程,这一切都由阻塞队列实现。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ProduceConsuerBlockingQueue {
//控制生产者启停,volatile保证可见性
private volatile boolean flag = true;
//多线程环境下保证原子性
AtomicInteger automicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
//构造传接口--适配7种阻塞队列
public ProduceConsuerBlockingQueue(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
//日志排查传入哪种阻塞队列
System.out.println(blockingQueue.getClass().getName());
}
public void produce() {
String data = null;
boolean result;
while(flag) {
try {
data = automicInteger.incrementAndGet() + "";
result = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() + "生产是否成功: "+result);
TimeUnit.SECONDS.sleep(1); //每秒生产一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 停止生产");
}
public void consumer() {
String data = null;
while(true) {
try {
data = blockingQueue.poll(2, TimeUnit.SECONDS);
if(data == null) {
System.out.println("2秒钟没有取到数据,消费退出.");
return;
}
System.out.println(Thread.currentThread().getName() + "消费成功: "+data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(5);
ProduceConsuerBlockingQueue pcbq = new ProduceConsuerBlockingQueue(blockingQueue);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 生产线程启动.");
pcbq.produce();
}, "procude").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "消费线程启动");
pcbq.consumer();
}, "consumer").start();
//运行5秒都停止
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("5秒结束,活动结束");
System.out.println();
System.out.println();
pcbq.stop();
}
}