阻塞队列

线程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. 小老板,我300M的网,网速很慢怎么办?

    首先考虑是不是DNS的问题: 我们先测试一下! 一.Google的DNS: 8.8.8.8 丢包严重 PASS但是扶墙的时候是必备的,如果有扶墙的需求的话可以备用. 二.百度DNS 180.76.76 ...

  2. OSG程序设计之Hello World 2.0

    现在为Hello World添加一些键盘响应事件. //需要多添加两个库:osgGAd.lib.osgd.lib 代码如下: #include <osgDB/ReadFile> #incl ...

  3. Linux之《荒岛余生》(一)准备篇

    xin片之争,已经暴露了中国xin的问题,我等码农束手无策:而在操作系统方面,成果也是乏善可陈:现如今酷炫的Web监控工具,让很多研发丧失了真正处理问题的能力. 越接近底层,就越接近真相,在计算机的世 ...

  4. node基础知识-说说对node的理解

    一.说说你对node的理解 从定义+特点+作用来说对node的理解 定义:node是基于Chrmo v8引擎的JavaScript运行环境; 特点:具有事件驱动,非阻塞I/O模型,高并发和轻量级,单线 ...

  5. hex文件格式总结

    hex文件格式总结 文章目录 hex文件格式总结 什么是hex文件? 文件格式 指令类型(Record type) 校验和 :04 02B0 00 92020008 AE :04 0000 05 08 ...

  6. STM32 IAP 升级官方资料汇总

    整理了一下SMT32标准外设库进行IAP升级的官方demo: 标准库版本 STM32F10xxx in-application programming using the USART (AN2557) ...

  7. Kitty Cloud(HTTP_RPC)的全局异常处理

    项目地址 https://github.com/yinjihuan/kitty-cloud 异常处理不用我讲,大家都清楚.单独的异常处理太繁琐,全局异常处理可以在一个应用中统一进行异常的处理,非常方便 ...

  8. 深入理解JS中的对象(二):new 的工作原理

    目录 序言 不同返回值的构造函数 深入 new 调用函数原理 总结 参考 1.序言 在 深入理解JS中的对象(一):原型.原型链和构造函数 中,我们分析了JS中是否一切皆对象以及对象的原型.原型链和构 ...

  9. 设计模式GOF23之单例模式

    单例模式的五种方式 主要:懒汉式,饿汉式 其他:双重检测锁(Double Checking模式),静态内部类,枚举模式 选取时机 延时加载,占用内部资源大:静态内部类好于懒汉 不延时加载,占用内部资源 ...

  10. python之unittest验证函数功能

    一.待验证脚本 首先,有如下三个脚本,分别对应三个函数 分别导入模块行,如下: from fuction1 import fu1 from fuction2 import fu2 from fucti ...