【JUC】阻塞队列&生产者和消费者
阻塞队列
线程1往阻塞队列添加元素【生产者】
线程2从阻塞队列取出元素【消费者】
当队列空时,获取元素的操作会被阻塞
当队列满时,添加元素的操作会被阻塞
阻塞队列的优势:在多线程领域,发生阻塞时,线程被挂起,条件满足时,被挂起的线程自动被唤醒。使用阻塞队列,不需要关心什么时候需要阻塞线程(开发效率差,可能存在线程不安全的误操作),阻塞队列这种数据结构可以自动控制。
源码架构:BlockingQueue有多个实现类,下面列举7个常用的。
ArrayBlockingQueue:由数组组成的有界阻塞队列
LinkedBlockingQueue:由链表组成的有界阻塞队列(大小默认值为Integer.MAX_VALUE:2147483647)
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue:不存储元素的阻塞队列,单个元素队列
LinkedTransferQueue:由链表组成的无界阻塞队列。
LinkedBlockingDeque:由链表组成的双向阻塞队列。
【线程池的底层就是标红的三个实现类实现的】
公共方法
抛出异常 | 特殊值 | 阻塞 | 超时 | |
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove(e) | poll(e) | take() | poll(time,unit) |
检查 | element() | peek() |
【抛出异常】
add方法:抛异常
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);//参数是容量
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.add("d"));
}
}
输出结果:
true
true
true
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
at day03CountDownLatch.BlockingQueueDemo.main(BlockingQueueDemo.java:13)
remove方法:抛异常
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);//参数是容量
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
}
输出:如果指定移除某元素,但是队列中不存在,不会抛异常,会返回false
true
a
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at day03CountDownLatch.BlockingQueueDemo.main(BlockingQueueDemo.java:12)
element方法:取出队首元素,如果为空返回异常:java.util.NoSuchElementException
【不抛异常 返回特定值】
offer方法:可以添加返回true,不可以添加返回false
poll方法:可以移除返回队首元素,并从队列移除,不可以移除返回null
peek方法:取出队首元素
【一直阻塞】
put方法:队满时,生产者线程往队列put元素,队列会一直阻塞生产线程直到可以put数据或响应中断退出。
take方法:队空时,消费者线程从队列取元素,队列会阻塞消费者线程直到队列可用。
【超时不候】
offer方法:定时阻塞,过时返回特殊值
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(2);//参数是容量
System.out.println(blockingQueue.offer("a",2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b",2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c",2L, TimeUnit.SECONDS));
}
输出:
true
true
false
SynchronousQueue
SynchronousQueue没有容量,不存储元素。每一个put操作必须等待take操作,否则不能继续添加元素,反之亦然。产生一个,消费一个。
【比如:我想吃螺丝粉了(take),我才去做螺蛳粉(put);我不做螺蛳粉(put),我就不能吃螺蛳粉(take)】
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 做螺蛳粉1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" 做螺蛳粉2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" 做螺蛳粉3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" 吃螺蛳粉"+blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" 吃螺蛳粉"+blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" 吃螺蛳粉"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
输出:如果线程B没有3个take,程序就会一直处于阻塞状态。
A 做螺蛳粉1
B 吃螺蛳粉1
A 做螺蛳粉2
B 吃螺蛳粉2
A 做螺蛳粉3
B 吃螺蛳粉3
生产者-消费者
高并发:线程操纵资源类。判断资源,生产者操作资源,唤醒通知消费者。严防多线程并发状态下的虚假唤醒。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* 一个初始值为零的变量,两个线程对其交替操作,一个加1一个减1,
*/
class Cakes {
private int cakeNumber = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try{
//判断 (多线程判断用while)
while(cakeNumber != 0){
//等待 不能生产
condition.await();
}
//进行操作(生产蛋糕)
cakeNumber++;
System.out.println(Thread.currentThread().getName()+"烹饪" + cakeNumber+"个蛋糕");
//通知唤醒
condition.signalAll();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
} public void decrement() throws InterruptedException {
lock.lock();
try{
//判断 (多线程判断用while)
while(cakeNumber ==0){
//等待 不能消费
condition.await();
}
//进行操作
cakeNumber--;
System.out.println(Thread.currentThread().getName()+"吃完蛋糕,还剩" + cakeNumber+"个蛋糕");
//通知唤醒
condition.signalAll();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
public class CakeShop {
public static void main(String[] args) {
Cakes shareData = new Cakes();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
shareData.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"厨师").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
shareData.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"顾客").start();
}
}
输出结果:
厨师烹饪1个蛋糕
顾客吃完蛋糕,还剩0个
厨师烹饪1个蛋糕
顾客吃完蛋糕,还剩0个
厨师烹饪1个蛋糕
顾客吃完蛋糕,还剩0个
厨师烹饪1个蛋糕
顾客吃完蛋糕,还剩0个
厨师烹饪1个蛋糕
顾客吃完蛋糕,还剩0个
什么是虚假唤醒(spurious wakeup)?
在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。所以,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。它们在没有被唤醒的情况下苏醒执行。
虽然虚假唤醒在pthread_cond_wait()函数中可以解决,为了发生概率很低的情况而降低边缘条件效率是不值得的,纠正这个问题会降低对所有基于它的所有更高级的同步操作的并发度。所以pthread_cond_wait()的实现上没有去解决它。
- 挂起等待条件变量来达到线程间同步通信的效果,而底层wait函数在设计之初为了不减慢条件变量操作的效率并没有去保证每次唤醒都是由notify触发,而是把这个任务交由上层应用去实现,即使用者需要定义一个while循环去判断是否条件真能满足程序继续运行的需求,当然这样的实现也可以避免因为设计缺陷导致程序异常唤醒的问题。
Condition的signal()和signalAll()的区别?
- signal 是随机解除一个等待集中的线程的阻塞状态;
- signalAll 是解除所有等待集中的线程的阻塞状态。
- signal 方法的效率会比 signalAll 高,但是它存在危险。因为它一次只解除一个线程的阻塞状态。如果等待集中有多个线程都满足了条件,也只能唤醒一个,其他的线程可能会导致死锁。
使用阻塞队列实现生产者和消费者
class CakeHouse {
private volatile boolean flag = true;
private AtomicInteger cakeNumber = new AtomicInteger();
BlockingQueue<String> blockingQueue = null; public CakeHouse(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
} public void closeDoor(){
this.flag = false;
} public void myProduct() throws InterruptedException {
String curCake = null;
boolean retValue;
while (flag){
curCake = cakeNumber.incrementAndGet() + " ";
retValue = blockingQueue.offer(curCake, 2L, TimeUnit.SECONDS);
if(retValue){
System.out.println(Thread.currentThread().getName()+":成功制作出"+"蛋糕"+curCake);
}else {
System.out.println(Thread.currentThread().getName()+":蛋糕"+curCake+"制作失败了");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println("蛋糕店快要关门了~厨师停止烹饪~");
} public void myConsumer() throws InterruptedException {
String res = null;
while (flag){
res = blockingQueue.poll(2L,TimeUnit.SECONDS);
if(null == res || res.equalsIgnoreCase("")){
flag = false;
System.out.println(Thread.currentThread().getName()+"耐心不足,离开蛋糕店。");
return;
}
System.out.println(Thread.currentThread().getName()+":我买到了蛋糕"+res);
System.out.println();
}
}
}
public class ProdConsumerBlockQueue {
public static void main(String[] args) {
CakeHouse myResource = new CakeHouse(new ArrayBlockingQueue<>(5));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"开始烹饪了");
try {
myResource.myProduct();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"厨师").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"来店里消费了");
try {
myResource.myConsumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"顾客").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------------------------------------");
System.out.println("店主:下班时间到了!");
myResource.closeDoor();
}
}
输出结果:
java.util.concurrent.ArrayBlockingQueue
厨师开始烹饪了
顾客来店里消费了
厨师:成功制作出蛋糕1
顾客:我买到了蛋糕1 厨师:成功制作出蛋糕2
顾客:我买到了蛋糕2 厨师:成功制作出蛋糕3
顾客:我买到了蛋糕3 厨师:成功制作出蛋糕4
顾客:我买到了蛋糕4 厨师:成功制作出蛋糕5
顾客:我买到了蛋糕5 -------------------------------------
店主:下班时间到了!
蛋糕店快要关门了~厨师停止烹饪~
顾客耐心不足,离开蛋糕店。
【JUC】阻塞队列&生产者和消费者的更多相关文章
- ArrayBlockingQueue 阻塞队列 生产者 与消费者案例
package com.originalityTest; import java.net.UnknownHostException; import java.util.ArrayList; impor ...
- 10 阻塞队列 & 生产者-消费者模式
原文:http://www.cnblogs.com/dolphin0520/p/3932906.html 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(Li ...
- 守护进程,互斥锁,IPC,队列,生产者与消费者模型
小知识点:在子进程中不能使用input输入! 一.守护进程 守护进程表示一个进程b 守护另一个进程a 当被守护的进程结束后,那么守护进程b也跟着结束了 应用场景:之所以开子进程,是为了帮助主进程完成某 ...
- JUC——阻塞队列
Queue是一个队列,而队列的主要特征是FIFO先进先出,要实现生产者与消费者模型,也可以采用队列来进行中间的缓冲读取,好处是:生产者可以一直不停歇的生产数据. BlockingQueue是Queue ...
- BlockingQueue 阻塞队列(生产/消费者队列)
1:BlockingQueue的继承关系 java.util.concurrent 包里的 BlockingQueue是一个接口, 继承Queue接口,Queue接口继承 Collection Blo ...
- 十五、.net core(.NET 6)搭建RabbitMQ消息队列生产者和消费者的简单方法
搭建RabbitMQ简单通用的直连方法 如果还没有MQ环境,可以参考上一篇的博客,在windows系统上的rabbitmq环境搭建.如果使用docker环境,可以直接百度一下,应该就一个语句就可以搞定 ...
- RabbitMQ消息队列生产者和消费者
概述 生产者生产数据至 RabbitMQ 队列,消费者消费 RabbitMQ 队列里的数据. 详细 代码下载:http://www.demodashi.com/demo/10723.html 一.准备 ...
- 第44天学习打卡(JUC 线程和进程 并发和并行 Lock锁 生产者和消费者问题 如何判断锁(8锁问题) 集合类不安全)
什么是JUC 1.java.util工具包 包 分类 业务:普通的线程代码 Thread Runnable 没有返回值.效率相比Callable相对较低 2.线程和进程 进程:一个程序.QQ.exe, ...
- Python 之并发编程之进程下(事件(Event())、队列(Queue)、生产者与消费者模型、JoinableQueue)
八:事件(Event()) # 阻塞事件: e = Event() 生成事件对象e e.wait() 动态给程序加阻塞,程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值 ...
随机推荐
- WCF客户端和服务的实现
WCF客户端和服务 ?服务器端: – 定义和实现服务契约 – 为服务类型构建ServiceHost实例,暴露endpoints – 打开通讯通道 ?客户端: – 需要服务契约的一个副本和关于endpo ...
- WordPress发布文章/页面时自动添加默认的自定义字段
如果你每篇文章或页面都需要插入同一个自定义字段和值,可以考虑在WordPress发布文章/页面时,自动添加默认的自定义字段.将下面的代码添加到当前主题的 functions.php 即可: 1 2 3 ...
- 数学--数论--HDU 2104 丢手绢(离散数学 mod N+ 剩余类 生成元)+(最大公约数)
The Children's Day has passed for some days .Has you remembered something happened at your childhood ...
- 数学--数论--Hdu 1452 Happy 2004(积性函数性质+和函数公式+快速模幂+乘法逆元)
Consider a positive integer X,and let S be the sum of all positive integer divisors of 2004^X. Your ...
- [CodeForces-259C] Little Elephant and Bits
C. Little Elephant and Bits time limit per test 2 seconds memory limit per test 256 megabytes input ...
- 01 微信小程序入门
一. 小程序介绍 微信小程序是腾讯于2017年1月9日推出的一种不需要下载安装即可在微信平台上使用的应用,主要提供给企业.政府.媒体.其他组织或个人的开发者在微信平台上提供服务. 微信小程序和微信的原 ...
- 最长递增子序列(Longest increasing subsequence)
问题定义: 给定一个长度为N的数组A,找出一个最长的单调递增子序列(不要求连续). 这道题共3种解法. 1. 动态规划 动态规划的核心是状态的定义和状态转移方程.定义lis(i),表示前i个数中以A[ ...
- thinkphp 5.x~3.x 文件包含漏洞分析
漏洞描述: ThinkPHP在加载模版解析变量时存在变量覆盖的问题,且没有对 $cacheFile 进行相应的消毒处理,导致模板文件的路径可以被覆盖,从而导致任意文件包含漏洞的发生. 主要还是变量覆盖 ...
- P2764 最小路径覆盖问题 网络流重温
P2764 最小路径覆盖问题 这个题目之前第一次做的时候感觉很难,现在好多了,主要是二分图定理不太记得了,二分图定理 知道这个之后就很好写了,首先我们对每一个点进行拆点,拆完点之后就是跑最大流,求出最 ...
- 这么多Linux版本,你究竟该怎么选择?
Linux有非常多的版本,比如世面上常见的有 Ubuntu.RedHat.Fedora.Centos等等,这么多的版本我们究竟该选哪一个呢?今天我带大家对各个版本进行一下分析和比较,帮助大家来做出更好 ...