我是大白(●—●),这是我开始学习记录大白Java软件攻城狮晋升之路的第三十八天,今天学习的是【尚硅谷】大厂必备技术之JUC并发编程
一、概述和架构
📗队列与栈
队列(queue):队列是一种先进先出(First In First Out)的线性表,简称FIFO。只允许在一端进行插入操作,而在另一端进行删除操作。允许插入的一端称为队尾,允许删除的一端称为队头。
栈(stack):栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。
📗阻塞队列的概念
阻塞队列,顾名思义,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
阻塞队列是一个队列,在数据结构中起的作用如下图:
- 当队列是空的,从队列中获取元素的操作将会被阻塞。
- 当队列是满的,从队列中添加元素的操作将会被阻塞。
- 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
- 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。
📗为什么需要BlockingQueue呢?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。
在concurrent包发布以前,在多线程环境下, 我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
📗阻塞队列的架构
如下图jdk文档所示,BlockingQueue是一个接口,继承的接口以及实现的类如下
二、分类和核心方法介绍
📗阻塞队列的分类
- 由数组结构组成的有界阻塞队列:
ArrayBlockingQueue
- 由链表结构组成的有界(一般大小为Integer.MAX_VALUE) 队列:
LinkedBlockingQueue
- 使用优先级队列实现的延迟无界阻塞队列:
DelayQueue
,只有队列中的元素当其指定的延迟时间到了,才能从队列中获取到该元素。 - 支持优先级排序的无界阻塞队列:
PriorityBlockingQueue
,基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定) - 不存储元素的阻塞队列,也即单个元素的队列:
SynchronousQueue
- 由链表组成的无界阻塞队列:
LinkedTransferQueue
- 由链表组成的双向阻塞对列:
LinkedBlockingDeque
ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。
阻塞队列中的核心方法
ps:纠正在一直阻塞里,应该将“当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产者线程直到put数据or响应中断退出。
”中put数据改为take数据,通过本人查询官方jdk中的介绍_Inserts the specified element at the tail of this queue, waiting for space to become available if the queue is full
. 翻译为_在此队列的尾部插入指定元素,等待队列已满时可用空间。也就是说需要等待队列被消费才可以继续put元素。
三、核心方法演示
分别演示上图中的add、element、remove;offer、peek、poll、超时offer、超时poll、put、take方法
/**
* @author Administrator
*
* ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)的原则对元素进行排序
* LinkedBlockingQueue:一个基于链表结构的有界阻塞队列(但是大小默认为Integer.MAX_VALUE),此队列按FIFO(先进先出)的原则对元素排序,吞吐量通常高于ArrayBlockingQueue.
* SynchronousQueue: 一个不存储元素的阻塞队列,也即一个元素的队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于
* DelayQueue: 延迟队列
*
*当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
*当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
*
* 试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
* 同样
* 试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增。
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws Exception{
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println("-----------------add remove element----------------");
//add 和 remove 在阻塞队列满的情况和空的情况为抛出异常
//add方法返回布尔值
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//底层调用的是peek方法
System.out.println(blockingQueue.element());
//remove方法返回元素
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println("---------------offer poll peek---------------------");
//offer、poll方法返回的是布尔值
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println("---------------延迟退出--------------------");
//当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出。
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));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println("---------------put take----------------------------");
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
System.out.println("===================================");
//当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程知道put数据or响应中断退出。
// blockingQueue.put("x");
System.out.println(blockingQueue);
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者进程直到队列可用
// blockingQueue.take();
}
}
运行结果如下:
当去掉最后put和take方法的注释之后,就可以发现线程被阻塞无法继续执行下面代码,大家可以自己尝试一下。