【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() [默认返回值 ...
随机推荐
- cobbler的网页操作
需求:安装一台服务器 1.指定两块网卡一块外网一块内网2.内网ip10.0.0.62外网为172.16.1.623.主机名为m02 开始吧! 1.添加镜像文件 2.创建ks文件 编写ks文件 附:ks ...
- 图论-网络流-最大流--POJ1273Drainage Ditches(Dinic)
Drainage Ditches Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 91585 Accepted: 3549 ...
- 一个简单的wed服务器SHTTPD(3)————SHTTPD多客户端支持的实现
//start from the very beginning,and to create greatness //@author: Chuangwei Lin //@E-mail:979951191 ...
- Minimum Euler Cycle(找规律+模拟)
\(给你一个nnn个结点的完全有向图,求其字典序最小的欧拉回路,输出lll到rrr之间的结点为多少.\) 模拟一下n=5的时候 开始肯定是1-2-1-3-1-4-1-5 注意这个时候不能再从5到1,否 ...
- thinkphp操作phpexcel问题
一.thinkphp引入PHPExcel到/Thinkphp/Library/Vendor/ 二.在控制器中引用 public function get_detail() { Vendor(" ...
- 王颖奇 20171010129《面向对象程序设计(java)》第十三周学习总结
实验十三 图形界面事件处理技术 实验时间 2018-11-22 1.实验目的与要求 (1) 掌握事件处理的基本原理,理解其用途: (2) 掌握AWT事件模型的工作机制: (3) 掌握事件处理的基 ...
- Redis 学习笔记(一) 字符串 SDS
SDS 简单动态字符串. SDS的结构: struct sdshdr{ int len;//记录BUF数组中已使用字节的数量 ,等于SDS所八寸字符串的长度 int free;//记录BUF数组中未使 ...
- 【Kafka】Consumer API
Consumer API Kafka官网文档给了基本格式 http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/client ...
- Day_11【集合】扩展案例4_删除长度大于5的字符串,删除元素包含0-9数字的字符串
分析以下需求,并用代码实现 1.定义ArrayList集合,存入多个字符串 如:"ab1" "123ad" "bca" "dadf ...
- CF#637 C. Nastya and Strange Generator
C. Nastya and Strange Generator 题意 有一个随机全排列生成器,给出你一个全排列,让判断是否可以通过这个生成器产生. 生成器工作方式: 第i步为数字i寻找位置pos. 首 ...