阻塞队列

线程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】阻塞队列&生产者和消费者的更多相关文章

  1. ArrayBlockingQueue 阻塞队列 生产者 与消费者案例

    package com.originalityTest; import java.net.UnknownHostException; import java.util.ArrayList; impor ...

  2. 10 阻塞队列 & 生产者-消费者模式

    原文:http://www.cnblogs.com/dolphin0520/p/3932906.html 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(Li ...

  3. 守护进程,互斥锁,IPC,队列,生产者与消费者模型

    小知识点:在子进程中不能使用input输入! 一.守护进程 守护进程表示一个进程b 守护另一个进程a 当被守护的进程结束后,那么守护进程b也跟着结束了 应用场景:之所以开子进程,是为了帮助主进程完成某 ...

  4. JUC——阻塞队列

    Queue是一个队列,而队列的主要特征是FIFO先进先出,要实现生产者与消费者模型,也可以采用队列来进行中间的缓冲读取,好处是:生产者可以一直不停歇的生产数据. BlockingQueue是Queue ...

  5. BlockingQueue 阻塞队列(生产/消费者队列)

    1:BlockingQueue的继承关系 java.util.concurrent 包里的 BlockingQueue是一个接口, 继承Queue接口,Queue接口继承 Collection Blo ...

  6. 十五、.net core(.NET 6)搭建RabbitMQ消息队列生产者和消费者的简单方法

    搭建RabbitMQ简单通用的直连方法 如果还没有MQ环境,可以参考上一篇的博客,在windows系统上的rabbitmq环境搭建.如果使用docker环境,可以直接百度一下,应该就一个语句就可以搞定 ...

  7. RabbitMQ消息队列生产者和消费者

    概述 生产者生产数据至 RabbitMQ 队列,消费者消费 RabbitMQ 队列里的数据. 详细 代码下载:http://www.demodashi.com/demo/10723.html 一.准备 ...

  8. 第44天学习打卡(JUC 线程和进程 并发和并行 Lock锁 生产者和消费者问题 如何判断锁(8锁问题) 集合类不安全)

    什么是JUC 1.java.util工具包 包 分类 业务:普通的线程代码 Thread Runnable 没有返回值.效率相比Callable相对较低 2.线程和进程 进程:一个程序.QQ.exe, ...

  9. Python 之并发编程之进程下(事件(Event())、队列(Queue)、生产者与消费者模型、JoinableQueue)

    八:事件(Event()) # 阻塞事件:    e = Event() 生成事件对象e    e.wait() 动态给程序加阻塞,程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值 ...

随机推荐

  1. Ajax各参数介绍及使用

    Ajax各参数介绍及使用 1. url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2. type: 要求为String类型的参数,请求方式(post或get)默认为get. ...

  2. Java——类的访问修饰符

    1.java中外部类的访问修饰符有如下四种: public,默认,abstract,final // public,默认,abstract,final. public class Test1 {} c ...

  3. 【Spark】部署流程的深度了解

    文章目录 Spark核心组件 Driver Executor Spark通用运行流程图 Standalone模式运行机制 Client模式流程图 Cluster模式流程图 On-Yarn模式运行机制 ...

  4. Centos7 网卡桥接

     一.在centos7主机创建用于虚拟化的网桥 1)增加 /etc/sysconfig/network-scripts/ifcfg-br0 DEVICE=br0BOOTPROTO=staticONBO ...

  5. JUC之ReentrantLock源码分析

    ReentrantLock:实现了Lock接口,是一个可重入锁,并且支持线程公平竞争和非公平竞争两种模式,默认情况下是非公平模式.ReentrantLock算是synchronized的补充和替代方案 ...

  6. LabVIEW动态添加控件

    综述: 事例1: 未执行: 执行后:

  7. [hdu5255]枚举

    思路:这题与csu1392题目类似,方法类似.枚举最高位,最低位和中间数字的长度,然后列等式,计算中间的数字,看长度是不是跟枚举的一致,需要注意的是中间数字可以有前导0,如果根据等式算出来的中间数字为 ...

  8. spring-boot+spring-cloud+maven-module 一个 maven多模块的微服务架构模版

    spring-boot-cloud-module spring-boot+spring-cloud+maven-module 一个 maven多模块的微服务架构模版. 新手上路的绝佳模版,只有必要的配 ...

  9. ZOOM火速收购加密公司Kaybase 能否补齐安全短板?

    近日,一直因为安全漏洞饱受批评的云视频会议协作工具ZOOM,,其公司全资收购一家初创企业Kaybase,以加强ZOOM系统的隐私保护和安全性.   Kaybase公司官网 2020年年初,随着疫情的蔓 ...

  10. Java代码生成器多表配置优化,增加自定义实体功能

    目录 前言 多表配置优化 自定义实体 杂谈 结语 前言   最近利用零碎的时间对代码生成器做了进一步更新:优化多表配置模块,增加自定义实体功能,美化单表和多表配置的UI界面,修复用户反馈的若干bug, ...