一、什么是阻塞队列
阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:
- 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
- 当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。
- 同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素或者全清空队列后,使队列重新变得空闲起来并后续新增。
二、阻塞队列有什么好处
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程又会被自动唤醒。
使用阻塞队列的好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手包办好了。
在concurrent包发布以前,在多线程环境下,每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
三、java 中的阻塞队列
3.1 架构介绍
3.2 种类分析
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。LinkedBlockingQueue:由链表结构组成的有界(但大小默认值是Integer.MAX_VALUE)阻塞队列。PriorityBlockingQueue:支持优先级排序的无界阻塞队列。DelayQueue:使用优先级队列实现的延迟无界阻塞队列。SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列。 (与其他BlcokingQueue不同,synchronousQueue是一个不存储元素的BlcokingQueue。每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。)LinkedTransferQueue:由链表结构组成的无界阻塞队列。LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
3.3 BlockingQueue 的核心方法
| 类型 | 说明 |
|---|---|
| 抛出异常 | 当阻塞队列满时,再往队列里面add插入元素会抛出 IllegalStateException:Queue full 当阻塞队列空时,再往队列 Remove 元素时候会抛出 NoSuchElementException |
| 特殊值 | 插入方法,成功返回true,失败返回 false 移除方法,成功返回元素,队列里面没有就返回 null |
| 一直阻塞 | 当阻塞队列满时,生产者继续往队列里面 put 元素,队列会一直阻塞,直到put数据或者响应中断退出。 当阻塞队列空时,消费者试图从队列 take 元素,队列会一直阻塞,消费者线程直到队列可用取出元素。 |
| 超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出。 |
四、生产者-消费者问题
4.1 传统的使用 await() 和 signal()
class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + " " + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + " " + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProdConsumer_traditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
shareData.increment();
}
}, "Thread-A").start();
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
shareData.decrement();
}
}, "Thread-B").start();
}
}
4.2 使用阻塞队列
class MyResource {
private volatile boolean flag = true;
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void producer() throws InterruptedException {
String data = null;
boolean resFlag;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
resFlag = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (resFlag) {
System.out.println(Thread.currentThread().getName() + " 生产队列, " + data + " 插入队列成功");
} else {
System.out.println(Thread.currentThread().getName() + " 生产队列, " + data + " 插入队列失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "flag = false, 生产动作结束");
}
public void consumer() throws InterruptedException {
String result = null;
while (flag) {
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (result == null || result.equalsIgnoreCase("")) {
flag = false;
System.out.println(Thread.currentThread().getName() + " 超过2秒没有取到,停止消费");
return;
}
System.out.println(Thread.currentThread().getName() + " 消费队列:" + result);
}
}
public void stop() {
this.flag = false;
}
}
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println("生产线程启动...");
try {
myResource.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "producer").start();
new Thread(() -> {
System.out.println("消费线程启动...");
try {
myResource.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "consumer").start();
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
myResource.stop();
System.out.println(Thread.currentThread().getName() + " main 线程终止生产-消费");
}
}