JUC -阻塞队列
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,次队列按 FIFO(先进先出)原则对匀速进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,次队列按 FIFO(先进先出)排序元素,吞吐量通常要高于 ArrayBlockingQueue。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作一直处于阻塞状态,吞吐量通常要高于
1,队列
2,阻塞队列
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
- 试图从空的阻塞队列中获取元素的线程将会被阻塞,知道其他的线程往空的队列插入新的元素。
同样 - 试图往已满的阻塞队列中添加新元素的线程同样会被阻塞,知道其他线程从队列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增。
2.1 阻塞队列有没有好的一面
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程会自动被唤醒。
为什么需要 BlockingQueue
- 好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue 都给你一手包办了。
在concurrent 包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
2.2 不得不阻塞,你如何管理
3,架构梳理,种类分析
架构梳理:
4,阻塞队列的核心方法
4.1 阻塞队列api之抛出异常组
package com.test.mianshi.queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 1,队列
*
* 2,阻塞队列
* 2.1 阻塞队列有没有好的一面
* 2.2 不得不阻塞,你如何管理
*/
public class BlockingQueueDemo {
public static void main(String[] args) {
// List list = new ArrayList();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// 队列的界限是3,当使用add方法添加元素时,会抛异常,Exception in thread "main" java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("x"));
// 检查队列中是否存在元素,存在时返回首个元素,当不存在元素时抛出异常,Exception in thread "main" java.util.NoSuchElementException
System.out.println(blockingQueue.element());
// 移除首个元素,返回被移除的元素
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// 移除指定元素,成功返回true,失败返回false
System.out.println(blockingQueue.remove("a"));
System.out.println(blockingQueue.remove());
// 当队列中没有元素时,调用remove方法会抛异常,Exception in thread "main" java.util.NoSuchElementException
// System.out.println(blockingQueue.remove());
}
}
4.2 阻塞队列api之返回布尔值组
public class BlockingQueueDemo {
public static void main(String[] args) {
// List list = new ArrayList();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
// 添加元素,成功返回true;失败返回false,不抛出异常
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("x"));
// 检查队列中是否有元素,有元素时,返回首元素的值
System.out.println(blockingQueue.peek());
// 移除元素时,成功返回移除的元素值;失败返回null,不抛出异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 检查队列中是否有元素,当队列为空时,返回null,不抛出异常
System.out.println(blockingQueue.peek());
}
}
4.3 阻塞队列api之阻塞和超时控制
阻塞:
public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
// List list = new ArrayList();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
// 不能存储 null 值
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
// 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据或响应中断退出
// blockingQueue.put("a");
System.out.println("========================>>");
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
// 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用
// blockingQueue.take();
}
}
超时:
public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
// List list = new ArrayList();
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
// 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
}
}
4.4 阻塞队列之同步SynchronnusQueue
SynchronousQueue 没有容量
与其他BlockingQueue 不同,SynchronousQueue 是一个不存储元素的 BlockingQueue。
每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
代码证明:
package com.test.mianshi.queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
*
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue blockingQueue = new SynchronousQueue();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"\tput 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"\tput 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"\tput 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+"\ttake 1");
blockingQueue.take();
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+"\ttake 2");
blockingQueue.take();
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+"\ttake 3");
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
}
}
控制台打印结果:
AAA put 1
BBB take 1
AAA put 2
BBB take 2
AAA put 3
BBB take 3
5,用在哪里
5.1 生产者消费者模式
5.1.1 线程通信之生产者消费者传统版
1,线程 操作(方法) 资源类
2,判断 干活 通知
3,防止虚假唤醒机制
Object 类的 wait() 方法可能存在虚假唤醒的情况,JDK1.8API文档原文如下,建议使用 while
当只有2个线程时,使用 if 判断时,不会产生虚假唤醒的情况;但是线程数扩到4时,就会产生虚假唤醒的情况。
package com.test.mianshi.juc.生产者与消费者.v2;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 题目:一个初始值为零的变量,两个线程对其交替操作,一个加1一个减1,来5轮
*
* 1,线程 操作(方法) 资源类
* 2,判断 干活 通知
* 3,防止虚假唤醒机制
*/
class ShareData{
private Integer number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment(){
// 1,上锁
lock.lock();
try {
// 判断
while (number != 0) {
condition.await();
}
// 干活
number++;
System.out.println(Thread.currentThread().getName()+"\t" + number);
// 唤醒
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement(){
// 1,上锁
lock.lock();
try {
// 判断
while (number == 0) {
condition.await();
}
// 干活
number--;
System.out.println(Thread.currentThread().getName()+"\t" + number);
// 唤醒
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for (int i = 0; i < 5; i++) {
shareData.increment();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
shareData.decrement();
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
shareData.increment();
}
}, "C").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
shareData.decrement();
}
}, "D").start();
}
}
如果判断时,把 while 换成 if 可能会产生虚假唤醒的情况,控制台部分输出结果如下:
A 1
B 0
A 1
B 0
A 1
B 0
A 1
B 0
A 1
C 2
B 1
D 0
阻塞队列版,生产者消费者模式
package com.test.mianshi.juc.生产者与消费者.v3;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
*/
class MyResource {
private volatile boolean FLAG = true; // 默认开启,进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列 " + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列 " + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println();
System.out.println(Thread.currentThread().getName()+"\t FLAG=false,生产动作停止");
}
public void myConsumer() throws Exception {
String result;
while (FLAG) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (StringUtils.isBlank(result)) {
FLAG = false;
System.out.println();
System.out.println(Thread.currentThread().getName() + "\t消费队列2秒钟没有取到蛋糕,退出。");
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列 " + result + " 成功");
}
}
public void stop() {
FLAG = false;
}
}
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(1));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
System.out.println();
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
myResource.stop();
}
}