Java 并发系列之七:java 阻塞队列(7个)
1. 基本概念
2. 实现原理
3. ArrayBlockingQueue
4. LinkedBlockingQueue
5. LinkedBlockingDeque
6. PriorityBlockingQueue
7. DelayQueue
8. SynchronousQueue
9. LinkedTransferQueue
10. 小总结
11. txt
Java阻塞队列(7个)
基本概念
阻塞队列是一个支持两个附加操作的队列
两个附加操作
支持阻塞的插入方法
当队列满时,队列会阻塞插入元素的线程,直到队列不满
支持阻塞的移除方法
当队列空时,队列会阻塞获取元素的线程,直到队列非空
阻塞队列的4中处理方式
抛出异常
定义
当队列满时,如果再往队列里插入元素,会抛出IllegalStateException
当队列空时,如果在从队列里移除元素,会抛出NoSuchElementException
具体方法
插入方法
add(e)
移除方法
remove()
检查方法
element()
返回特殊值
定义
往队列插入元素时,会返回元素是否插入成功,成功返回true
从队列移除元素时,如果没有则会返回null
具体方法
插入方法
offer(e)
移除方法
poll()
检查方法
peek()
一直阻塞
定义
当队列满时,如果生产者线程再往队列里put插入元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出
当队列空时,如果消费者线程从队列里take移除元素,队列会阻塞住消费者线程,直到队列不为空
具体方法
插入方法
put(e)
移除方法
take()
检查方法
不可用
超时退出
定义
当队列满时,如果生产者线程再往队列里插入元素,队列会阻塞生产者线程一段时间,超过了指定的时间,生产者线程就会退出
具体方法
插入方法
offer(e, time, unit)
移除方法
poll( time, unit )
检查方法
不可用
注意: 如果是无界阻塞队列, 队列不可能出现满的情况,使用put或offer方法永远不会被阻塞,而且使用offer方法时,永远返回true
应用场景
生产者消费者场景
阻塞队列就是生产者用来存放元素,消费者用来获取元素的容器
实现原理
使用通知模式
当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用
小总结
一个抽象类
AbstractQueue,改类在Queue接口中扮演着非常重要的作用,该类提供了对queue操作的骨干实现
一个接口
BlockingQueue继承java.util.Queue为阻塞队列的核心接口,提供了在多线程环境下的出列、入列操作,作为使用者,则不需要关心队列在什么时候阻塞线程,什么时候唤醒线程,所有一切均由BlockingQueue来完成。
七个阻塞队列
ArrayBlockingQueue
一个由数组组成的有界阻塞队列
LinkedBlockingQueue
一个由链表组成的有界阻塞队列
LinkedBlockingDeque
一个由链表组成的双向阻塞队列
PriorityBlockingQueue
一个支持优先级排序的无界阻塞队列
DelayQueue
一个使用优先级队列实现的无界阻塞队列
SynchronousQueue
一个不存储元素的阻塞队列
LinkedTransferQueue
一个由链表组成的无界阻塞队列
即可以像其他的BlockingQueue一样有容量又可以像SynchronousQueue一样不会锁住整个队列
有界
对读或者写都是锁上整个队列,在并发量大的时候,各种锁是比较耗资源和耗时间的
LinkedTransferQueue
特性
一个由链表组成的无界阻塞队列,FIFO
即可以像其他的BlockingQueue一样有容量又可以像SynchronousQueue一样不会锁住整个队列
相对于其他的阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法
LinkedTransferQueue是一个聪明的队列。它是ConcurrentLinkedQueue(无界非阻塞队列)、SynchronousQueue (公平模式下)、无界的LinkedBlockingQueues等的超集。
LinkedTransferQueue采用一种预占模式。什么意思呢?有就直接拿走,没有就占着这个位置直到拿到或者超时或者中断。即消费者线程到队列中取元素时,如果发现队列为空,则会生成一个null节点,然后park住等待生产者。后面如果生产者线程入队时发现有一个null元素节点,这时生产者就不会入列了,直接将元素填充到该节点上,唤醒该节点的线程,被唤醒的消费者线程拿东西走人。
Node节点
isData:表示该节点是存放数据还是获取数据
item:存放数据,isData为false时,该节点为null,为true时,匹配后,该节点会置为null
next:指向下一个节点
waiter:park住消费者线程,线程就放在这里
重要操作
LinkedTransferQueue提供了add、put、offer三类方法,用于将元素插入队列中
LinkedTransferQueue提供了poll、take方法用于出列元素
transfer方法
如果当前有消费者正在等待接收元素,transfer可以把生产者传入的元素立刻传输给消费者
如果没有,会将元素存放在队列的tail节点,并等到该元素被消费者消费(CAS)了才返回
tryTransfer方法
试探生产者传入的元素是否能直接传给消费者
如果没有消费者等待接受元素,则返回false
两者的区别是tryTransfer方法无论消费者是否接收,方法立即返回
transfer方法必须等到消费者消费了才返回
SynchronousQueue
特性
一个不存储元素的阻塞队列
SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
因为没有容量,所以对应 peek, contains, clear, isEmpty … 等方法其实是无效的。例如clear是不执行任何操作的,contains始终返回false,peek始终返回null。
SynchronousQueue分为公平和非公平,默认情况下采用非公平性访问策略,当然也可以通过构造函数来设置为公平性访问策略(为true即可,FIFO)。
SynchronizedQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue
若使用 TransferQueue, 则队列中永远会存在一个 dummy node
应用场景
传递性场景
负责把生产者线程处理的数据直接传递给消费者线程
生产者的线程和消费者的线程同步以传递某些信息、事件或者任务
内部类
Transferer
Transferer为SynchronousQueue的内部类,它提供了一个方法transfer(),该方法定义了转移数据的规范
TransferQueue
SynchronousQueue采用队列TransferQueue来实现公平性策略
TransferStack
采用堆栈TransferStack来实现非公平性策略
TransferQueue、TransferStack继承Transferer
两种都是通过链表实现的,其节点分别为QNode,SNode
Exchanger 可能被视为 SynchronousQueue 的双向形式。
它提供一个同步点,用于进行线程间成对配对及交换数据,
DelayQueue
特性
一个使用优先级队列实现的无界阻塞队列,使用PriorityQueue来实现,延时获取
DelayQueue是一个支持延时获取元素的无界阻塞队列。里面的元素全部都是“可延期”的元素,列头的元素是最先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期到时才能够从队列中取元素。
PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高。
与Exchanger有一拼
应用场景
缓存系统的设计
可是用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了
定时任务调度
使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现的。
内部结构|重要参数
可重入锁ReentrantLock
用于阻塞和通知的Condition对象
根据Delay时间排序的优先级队列:PriorityQueue
用于优化阻塞通知的线程元素leader,通过leader来减少不必要阻塞。
实现
继承Delay接口
创建对象,初始化基础数据,time记录当前对象延迟到什么时候能用,sequenceNumber表示元素在队列中的先后顺序
实现getDelay方法,该方法返回当前元素还需要延时多长时间,单位是纳秒
实现compareTo方法来指定元素的顺序
实现延时阻塞队列
当消费者线程从队列里获取元素时,如果元素没有达到延时时间,就阻塞当前线程
重要操作
offer
offer(E e)就是往PriorityQueue中添加元素,同PriorityBlockingQueue,但是注意有一点如果当前元素的对首元素(优先级最高),leader设置为null,唤醒所有等待线程
take
首先是获取对首元素,如果对首元素的延时时间 delay <= 0 ,则可以出对了,直接return即可。否则设置first = null,这里设置为null的主要目的是为了避免内存泄漏。如果 leader != null 则表示当前有线程占用,则阻塞,否则设置leader为当前线程,然后调用awaitNanos()方法超时等待。
PriorityBlockingQueue
特性
一个支持优先级排序的无界阻塞队列,支持优先级
PriorityBlockingQueue底层采用二叉堆来实现的,添加操作是添加到最后不断“上冒”,删除操作是删掉根将最后一个提到根不断“下掉”。
内部仍然采用可重入锁ReentrantLock来实现同步机制,但是这里只有一个notEmpty的Condition,因为无界队列,插入总是会成功,除非资源耗尽
默认情况下元素采用自然顺序升序排序,当然我们也可以通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。
分类
- 最大堆
- 父节点的键值总是大于或等于任何一个子节点的键值
- 最小堆
- 父节点的键值总是小于或等于任何一个子节点的键值
LinkedBlockingDeque
特性
一个由链表组成的双向阻塞队列,LinkedBlockingDeque支持FIFO、FILO两种操作方式。
LinkedBlockingDeque底层实现机制是通过互斥锁ReentrantLock 来实现,notEmpty 、notFull 两个Condition做协调生产者、消费者问题。
LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。
多了一个操作队列的入口,在多线程同时入队时,减少了一般的竞争
内部类Node
E item;
Node<E> prev;
Node<E> next;
重要操作
入队
putFirst(E e) :将指定的元素插入此双端队列的开头,必要时将一直等待可用空间。
putLast(E e) :将指定的元素插入此双端队列的末尾,必要时将一直等待可用空间。
出队
pollFirst():获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。
pollLast():获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。
其他
LinkedBlockingDeque大部分方法都是通过linkFirst、linkLast、unlinkFirst、unlinkLast这四个方法来实现的,因为是双向队列,所以他们都是针对first、last的操作
LinkedBlockingDeque 的add、put、offer、take、peek、poll系列方法都是通过调用XXXFirst,XXXLast方法。
应用
工作窃取模式中
LinkedBlockingQueue
特性
一个由链表组成的有界阻塞队列,该队列采用FIFO的原则对元素进行排序添加的。
LinkedBlockingQueue是通过互斥锁ReentrantLock 来实现,notEmpty 、notFull 两个Condition做协调生产者、消费者问题。
默认和最大长度是Integer.MAX_VALUE
ArrayBlockingQueue
特性
ArrayBlockingQueue,一个由数组实现的有界阻塞队列。该队列采用FIFO的原则对元素进行排序添加的。
ArrayBlockingQueue内部使用可重入锁ReentrantLock + Condition来完成多线程环境的并发操作。
ArrayBlockingQueue为有界且固定,其大小在构造时由构造函数来决定,确认之后就不能再改变了。
ArrayBlockingQueue支持对等待的生产者线程和使用者线程进行排序的可选公平策略,但是在默认情况下不保证线程公平的访问,在构造时可以选择公平策略(fair = true)。公平性通常会降低吞吐量,但是减少了可变性和避免了“不平衡性”。
重要参数
items,一个定长数组,维护ArrayBlockingQueue的元素
takeIndex,int,为ArrayBlockingQueue队首位置
putIndex,int,ArrayBlockingQueue队尾位置
count,元素个数
lock,锁,ArrayBlockingQueue出列入列都必须获取该锁,两个步骤公用一个锁
notEmpty,出列条件:不空
notFull,入列条件:不满
重要操作
入队
add(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException
offer(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false
offer(E e, long timeout, TimeUnit unit) :将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间
put(E e) :将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间
出队
poll() :获取并移除此队列的头,如果此队列为空,则返回 null
poll(long timeout, TimeUnit unit) :获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)
remove(Object o) :从此队列中移除指定元素的单个实例(如果存在)
take() :获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)
12. 参考网址
- 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
- 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
- http://ifeve.com/the-art-of-java-concurrency-program-1/
- Java并发学习系列-绪论
- Java并发编程实战
- 死磕 Java 并发精品合集
Java 并发系列之七:java 阻塞队列(7个)的更多相关文章
- 25、Java并发性和多线程-阻塞队列
以下内容转自http://ifeve.com/blocking-queues/: 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操 ...
- Java并发容器之非阻塞队列ConcurrentLinkedQueue
参考资料:http://blog.csdn.net/chenchaofuck1/article/details/51660521 实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,阻塞队列就是 ...
- Java并发编程()阻塞队列和生产者-消费者模式
阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到有空间可用:如果队列为空,那么take方法将会阻塞直到有元素可用.队列可以 ...
- java并发系列(八)-----java异步编程
同步计算与异步计算 从多个任务的角度来看,任务是可以串行执行的,也可以是并发执行的.从单个任务的角度来看,任务的执行方式可以是同步的,也可以是异步的. Runnable.Callable.Future ...
- java并发系列(六)-----Java并发:volatile关键字解析
在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...
- 【Java并发系列】--Java内存模型
Java内存模型 1 基本概念 程序:代码,完成某一个任务的代码序列(静态概念) 进程:程序在某些数据上的一次运行(动态) 线程:一个进程有一个或多个线程组成(占有资源的独立单元) 2 JVM与线程 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- Java 并发系列之二:java 并发机制的底层实现原理
1. 处理器实现原子操作 2. volatile /** 补充: 主要作用:内存可见性,是变量在多个线程中可见,修饰变量,解决一写多读的问题. 轻量级的synchronized,不会造成阻塞.性能比s ...
随机推荐
- 02、策略模式(Strategy)
一.概念: 策略是为达到某一目的而采取的手段或方法,策略模式的本质是目标与手段的分离, 手段不同而最终达成的目标一致.客户只关心目标而不在意具体的实现方法, 实现方法要根据具体的环境因素而变化. 二. ...
- NetCore HttpClient The SSL connection could not be established, see inner exception
之前遇到一个问题 https://www.cnblogs.com/leoxjy/p/10201046.html 在centos 7.x HttpClient访问会出问题 The SSL conne ...
- linux centos无法删除网站根目录下的.user.ini解决办法
.user.ini文件在执行rm -rf时,提示无法删除 解决办法 首先了解下chattr命令的作用:不让用户修改.删除文件等. -i选项:设定文件不能被删除.改名.设定链接关系,同时不能写入或新增内 ...
- PIE SDK内存矢量数据的创建
1.功能简介 GIS将地理空间数据表示为矢量数据和栅格数据.矢量数据模型使用点.线和多边形来表示具有清晰空间位置和边界的空间要素,如控制点.河流和宗地等,每个要素被赋予一个ID,以便与其属性相关联.栅 ...
- 性能测试-MySQL性能查看(转)
mysql查看数据库性能常用命令 mysql> show global status; 可以列出MySQL服务器运行各种状态值,另外,查询MySQL服务器配置信息语句: mysql> sh ...
- django logger转载
https://www.cnblogs.com/jiangchunsheng/p/8986452.html https://www.cnblogs.com/jeavy/p/10926197.html ...
- javascript之BOM对象(三其他对象)
一.navigator对象 navigator使用来识别浏览器的,是所有支持javascript的浏览器所共有的.与BOM的其他对象不同,每个浏览器的navigator对象都有一套自己的属性. 常见的 ...
- springboot日常问题处理手记
springboot启动问题 1.@Autowired报错Could not autowire. No beans of xxx 解决:只需在DAO接口加上@Component 或者 @Reposit ...
- 第一部分day2-for、while、数据类型(字符串、列表、元组)
数据类型 数据类型的初识 1.数字 整数 int (integer) 整型 (注:python3 不区分整型和长整型,统一称之为整型) 长整型 float(浮点型) complex(复数) 是由实数和 ...
- 并发编程 || Java线程详解
通用线程模型 在很多研发当中,实际应用是基于一个理论再进行优化的.所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解. ...