【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() [默认返回值 ...
随机推荐
- THU OS
前言 最近在学清华的OS课程,实验课做的真厉害. 我们的操作系统课就是写一个系统调用,改下进程软中断通信的代码,代码量不足500...清华是借鉴MIT的课,实验课都是做一个完整的小型操作系统. 一共8 ...
- python(读取 excel 操作 xlrd 模块)
一.安装 xlrd 模块 到 python 官网下载 http://pypi.python.org/pypi/xlrd 模块安装,前提是已经安装了 python 环境. 或者在 cmd 窗口 pip ...
- 数据库SQL---实体-联系模型(E-R模型)
1.E-R模型的基本概念与表示 1)实体与实体集 (1)实体:可以是有形的.实在的事物(如每个员工),可以是抽象的.概念上存在的事物(如一个部门). (2)实体的特征:独立存在.可区别于其他实体. ( ...
- 自己封装函数,实现数组的内置方法indexOf的功能
在学习或开发过程中,经常会有朋友需要使用到一个数组方法-indexOf,这里我们先来谈谈它的功能:返回指定数据所在的索引,如果没有则返回-1. 那么我们在使用时通常是直接使用它这个数组内置方法 今天这 ...
- 【Hadoop离线基础总结】HDFS入门介绍
HDFS入门介绍 概述 HDFS全称为Hadoop Distribute File System,也就是Hadoop分布式文件系统,是Hadoop的核心组件之一. 分布式文件系统是横跨在多台计算机上的 ...
- NLTK数据包下载频繁报错——解决方法
问题描述:Nltk下载数据包,安装频繁报错. import nltk nltk.download() 运行上面的代码,下载nltk的数据包.但是在下载过程中因为各种问题导致网络传输失败,下载不成功. ...
- 微信小程序使用GoEasy实现websocket实时通讯
不需要下载安装,便可以在微信好友.微信群之间快速的转发,用户只需要扫码或者在微信里点击,就可以立即运行,有着近似APP的用户体验,使得微信小程序成为全民热爱的好东西~ 同时因为微信小程序使用的是Jav ...
- Linux内核驱动学习(一)编写最简单Linux内核模块HelloWorld
文章目录 准备工作 什么是内核模块 编写 hello.c 模块编译 相关指令 测试结果 模块加载 模块卸载 准备工作 在进行以下操作前,首先我准备了一台电脑,并且安装了虚拟机,系统是Ubuntu16. ...
- .NETcore中使用jwt来对api进行身份验证
对于 登陆,身份,授权这之类的操作,我们最常用的几种方法无非就是 cookie session token 这三者的差别 https://www.cnblogs.com/moyand/p/904797 ...
- [CodeForces 300D Painting Square]DP
http://codeforces.com/problemset/problem/300/D 题意:每一次操作可以选一个正方形,令边长为n,如果n为奇数那么可以从中间画一个十字,分成4个大小相等的边长 ...