首页 技术 正文
技术 2022年11月9日
0 收藏 305 点赞 4,484 浏览 6460 个字

Java 并发系列之七:java 阻塞队列(7个)

1. 基本概念

Java 并发系列之七:java 阻塞队列(7个)

2. 实现原理

Java 并发系列之七:java 阻塞队列(7个)

3. ArrayBlockingQueue

Java 并发系列之七:java 阻塞队列(7个)

4. LinkedBlockingQueue

Java 并发系列之七:java 阻塞队列(7个)

5. LinkedBlockingDeque

Java 并发系列之七:java 阻塞队列(7个)

6. PriorityBlockingQueue

Java 并发系列之七:java 阻塞队列(7个)

7. DelayQueue

Java 并发系列之七:java 阻塞队列(7个)

8. SynchronousQueue

Java 并发系列之七:java 阻塞队列(7个)

9. LinkedTransferQueue

Java 并发系列之七:java 阻塞队列(7个)

10. 小总结

Java 并发系列之七:java 阻塞队列(7个)

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. 参考网址

  1. 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
  2. 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
  3. http://ifeve.com/the-art-of-java-concurrency-program-1/
  4. Java并发学习系列-绪论
  5. Java并发编程实战
  6. 死磕 Java 并发精品合集
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,501
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,915
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,747
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,505
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,143
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,306