阻塞队列
概念
队列
队列就可以想成是一个数组,从一头进入,一头出去,排队买饭
阻塞队列
BlockingQueue 阻塞队列,排队拥堵,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如下图所示:
线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素
-
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞
- 当蛋糕店的柜子空的时候,无法从柜子里面获取蛋糕
-
当阻塞队列是满时,从队列中添加元素的操作将会被阻塞
- 当蛋糕店的柜子满的时候,无法继续向柜子里面添加蛋糕了
也就是说 试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其它线程往空的队列插入新的元素
同理,试图往已经满的阻塞队列中添加新元素的线程,直到其它线程往满的队列中移除一个或多个元素,或者完全清空队列后,使队列重新变得空闲起来,并后续新增
为什么要用?
去海底捞吃饭,大厅满了,需要进候厅等待,但是这些等待的客户能够对商家带来利润,因此我们非常欢迎他们阻塞
在多线程领域:所谓的阻塞,在某些清空下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动唤醒
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都帮你一手包办了
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须自己取控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
架构
// 你用过List集合类 // ArrayList集合类熟悉么? // 还用过 CopyOnWriteList 和 BlockingQueue
BlockingQueue阻塞队列是属于一个接口,底下有七个实现类
- ArrayBlockQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:由链表结构组成的有界(但是默认大小 Integer.MAX_VALUE)的阻塞队列
- 有界,但是界限非常大,相当于无界,可以当成无界
- PriorityBlockQueue:支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列
- SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列
- 生产一个,消费一个,不存储元素,不消费不生产
- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
这里需要掌握的是:ArrayBlockQueue、LinkedBlockingQueue、SynchronousQueue
BlockingQueue核心方法
抛出异常 | 当阻塞队列满时:在往队列中add插入元素会抛出 IIIegalStateException:Queue full 当阻塞队列空时:再往队列中remove移除元素,会抛出NoSuchException |
---|---|
特殊性 | 插入方法,成功true,失败false 移除方法:成功返回出队列元素,队列没有就返回空 |
一直阻塞 | 当阻塞队列满时,生产者继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出, 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。 |
超时退出 | 当阻塞队列满时,队里会阻塞生产者线程一定时间,超过限时后生产者线程会退出 |
SynchronousQueue
SynchronousQueue没有容量,与其他BlockingQueue不同,SynchronousQueue是一个不存储的BlockingQueue,每一个put操作必须等待一个take操作,否者不能继续添加元素
阻塞队列的用处
生产者消费者模式
一个初始值为0的变量,两个线程对其交替操作,一个加1,一个减1,来5轮
关于多线程的操作,我们需要记住下面几句
- 线程 操作 资源类
- 判断 干活 通知
- 防止虚假唤醒机制
我们下面实现一个简单的生产者消费者模式
/**
* @author HX
* @title: ProdConsumerTraditionDemo
* @projectName spring_cloud1x
* @date 2022/5/11 14:30
* <p>
* 一个初始值为0的变量,两个线程对其交替操作,一个加1,一个减1,来5轮
*/
class ShareData {
private Integer num = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//加一
public void add() {
lock.lock();
try {
// 判断 while 防止虚假唤醒机制
while (num != 0) {
condition.await();
}
// 干活
num++;
System.out.println(Thread.currentThread().getName() + "\t " + num);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//减一
public void reduce() {
lock.lock();
try {
// 判断 while 防止虚假唤醒机制
while (num == 0) {
condition.await();
}
// 干活
num--;
System.out.println(Thread.currentThread().getName() + "\t " + num);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProdConsumerTraditionDemo {
/**
* @param args 线程 操作 资源类
* 判断 干活 通知
* 防止虚假唤醒机制
*/
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.add();
}
}, "AAAA").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.reduce();
}
}, "BBBB").start();
}
}
生成者和消费者3.0
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,则这会给我们的程序带来不小的时间复杂度
现在我们使用新版的阻塞队列版生产者和消费者,使用:volatile、CAS、atomicInteger、BlockQueue、线程交互、原子引用
/**
* @author HX
* @title: ProdConsumerBlockingQueueDemo
* @projectName spring_cloud1x
* @date 2022/5/13 9:45
* 生产者消费者 阻塞队列版
* 使用:volatile、CAS、atomicInteger、BlockQueue、线程交互、原子引用
*/
class MySource {
private volatile boolean FLAGE = true;
private AtomicInteger atomicInteger = new AtomicInteger(0);
BlockingQueue<String> queue = null;
// 而应该采用依赖注入里面的,构造注入方法传入
public MySource(BlockingQueue<String> queue) {
this.queue = queue;
System.out.println(Thread.currentThread().getName());
}
//生产
public void myProd() throws Exception {
String data = "";
boolean offer = false;
//判断
while (FLAGE) {
data = atomicInteger.incrementAndGet() + "";
offer = queue.offer(data, 2L, TimeUnit.SECONDS);
if (offer) {
System.out.println(Thread.currentThread().getName() + "\t 插入成功 值--》" + data);
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入失败");
}
//2秒执行一次
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName() + "\t 停止生产,表示FLAG=false,生产介绍");
}
/**
* 消费
*
* @throws Exception
*/
public void myConsumer() throws Exception {
String retValue;
// 多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
// 当FLAG为true的时候,开始生产
while (FLAGE) {
// 取值 2秒有效期
retValue = queue.poll(2L, TimeUnit.SECONDS);
Thread.sleep(50);
if (retValue != null && !retValue.equals("")) {
System.out.println(Thread.currentThread().getName() + "\t 消费队列:" + retValue + "成功");
System.out.println("");
} else {
FLAGE = false;
System.out.println(Thread.currentThread().getName() + "\t 消费失败,队列中已为空,退出");
System.out.println("");
// 退出消费队列
return;
}
}
}
public void stop() {
FLAGE = false;
}
}
public class ProdConsumerBlockingQueueDemo {
public static void main(String[] args) {
MySource mySource = new MySource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
try {
mySource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
}, "AAAA").start();
new Thread(() -> {
try {
mySource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "BBBB").start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("");
System.out.println("10秒中后,生产和消费线程停止,线程结束");
System.out.println();
mySource.stop();
}