Java多线程:队列与阻塞队列
1. 什么是阻塞队列
阻塞队列(BlockingQueue)是 Java 5 并发新特性中的内容,阻塞队列的接口是 java.util.concurrent.BlockingQueue,它提供了两个附加操作:当队列中为空时,从队列中获取元素的操作将被阻塞;当队列满时,向队列中添加元素的操作将被阻塞。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器。
阻塞队列提供了四种操作方法:
- 抛出异常:当队列满时,再向队列中插入元素,则会抛出IllegalStateException异常。当队列空时,再向队列中获取元素,则会抛出NoSuchElementException异常。
- 返回特殊值:当队列满时,向队列中添加元素,则返回false,否则返回true。当队列为空时,向队列中获取元素,则返回null,否则返回元素。
- 一直阻塞:当阻塞队列满时,如果生产者向队列中插入元素,则队列会一直阻塞当前线程,直到队列可用或响应中断退出。当阻塞队列为空时,如果消费者线程向阻塞队列中获取数据,则队列会一直阻塞当前线程,直到队列空闲或响应中断退出。
- 超时退出:当队列满时,如果生产线程向队列中添加元素,则队列会阻塞生产线程一段时间,超过指定的时间则退出返回false。当队列为空时,消费线程从队列中移除元素,则队列会阻塞一段时间,如果超过指定时间退出返回null。
2. Java中的阻塞队列
下面分别简单介绍一下:
ArrayBlockingQueue:是一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序。支持公平锁和非公平锁。【注:每一个线程在获取锁的时候可能都会排队等待,如果在等待时间上,先获取锁的线程的请求一定先被满足,那么这个锁就是公平的。反之,这个锁就是不公平的。公平的获取锁,也就是当前等待时间最长的线程先获取锁】
- LinkedBlockingQueue:一个由链表结构组成的有界队列,此队列的长度为Integer.MAX_VALUE。此队列按照先进先出的顺序进行排序。
- PriorityBlockingQueue: 一个支持线程优先级排序的无界队列,默认自然序进行排序,也可以自定义实现compareTo()方法来指定元素排序规则,不能保证同优先级元素的顺序。
- DelayQueue: 一个实现PriorityBlockingQueue实现延迟获取的无界队列,在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。(DelayQueue可以运用在以下应用场景:1.缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。2.定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。)
- SynchronousQueue: 一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
- LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列,相当于其它队列,LinkedTransferQueue队列多了transfer和tryTransfer方法。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半。
Java中线程安全的内置队列还有两个:ConcurrentLinkedQueue和LinkedTransferQueue,它们使用了CAS这种无锁的方式来实现了线程安全的队列。无锁的方式性能好,但是队列是无界的,用在生产系统中,生产者生产速度过快,可能导致内存溢出。有界的阻塞队列ArrayBlockingQueue和LinkedBlockingQueue,为了减少Java的垃圾回收对系统性能的影响,会尽量选择array/heap格式的数据结构。这样的话就只剩下ArrayBlockingQueue。(先埋个坑在这儿,近来接触到了disruptor,感觉妙不可言。disruptor)
3. 阻塞队列的实现原理
这里分析下ArrayBlockingQueue的实现原理。
构造方法:
- ArrayBlockingQueue(int capacity);
- ArrayBlockingQueue(int capacity, boolean fair);
- ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
ArrayBlockingQueue提供了三种构造方法,参数含义如下:
- capacity:容量,即队列大小。
- fair:是否公平锁。
- c:队列初始化元素,顺序按照Collection遍历顺序。
插入元素:
- public void put(E e) throws InterruptedException {
- checkNotNull(e);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- while (count == items.length)
- notFull.await();
- enqueue(e);
- } finally {
- lock.unlock();
- }
- }
从源码可以看出,生产者首先获得锁lock,然后判断队列是否已经满了,如果满了,则等待,直到被唤醒,然后调用enqueue插入元素。
- private void enqueue(E x) {
- // assert lock.getHoldCount() == 1;
- // assert items[putIndex] == null;
- final Object[] items = this.items;
- items[putIndex] = x;
- if (++putIndex == items.length)
- putIndex = 0;
- count++;
- notEmpty.signal();
- }
- 以上是enqueue的实现,实现的操作是插入元素到一个环形数组,然后唤醒notEmpty上阻塞的线程。
获取元素:
- public E take() throws InterruptedException {
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- while (count == 0)
- notEmpty.await();
- return dequeue();
- } finally {
- lock.unlock();
- }
- }
从源码可以看出,消费者首先获得锁,然后判断队列是否为空,为空,则等待,直到被唤醒,然后调用dequeue获取元素。
- private E dequeue() {
- // assert lock.getHoldCount() == 1;
- // assert items[takeIndex] != null;
- final Object[] items = this.items;
- @SuppressWarnings("unchecked")
- E x = (E) items[takeIndex];
- items[takeIndex] = null;
- if (++takeIndex == items.length)
- takeIndex = 0;
- count--;
- if (itrs != null)
- itrs.elementDequeued();
- notFull.signal();
- return x;
- }
以上是dequeue的实现,获取环形数组当前takeIndex的元素,并及时将当前元素置为null,设置下一次takeIndex的值takeIndex++,然后唤醒notFull上阻塞的线程。
还有其他方法offer(E e)
、poll()
、add(E e)
、remove()
、 offer(E e, long timeout, TimeUnit unit)
等的实现,因为常用take和put,这些方法就不一一赘述了。
4. 阻塞队列的基本使用
- /**
- * Created by noly on 2017/5/19.
- */
- public class BlockingQueueTest {
- public static void main (String[] args) {
- ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);
- Consumer consumer = new Consumer(queue);
- Producer producer = new Producer(queue);
- producer.start();
- consumer.start();
- }
- }
- class Consumer extends Thread {
- private ArrayBlockingQueue<Integer> queue;
- public Consumer(ArrayBlockingQueue<Integer> queue){
- this.queue = queue;
- }
- @Override
- public void run() {
- while(true) {
- try {
- Integer i = queue.take();
- System.out.println("消费者从队列取出元素:" + i);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- class Producer extends Thread {
- private ArrayBlockingQueue<Integer> queue;
- public Producer(ArrayBlockingQueue<Integer> queue){
- this.queue = queue;
- }
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) {
- try {
- queue.put(i);
- System.out.println("生产者向队列插入元素:" + i);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
如果不使用阻塞队列,使用Object.wait()和Object.notify()、非阻塞队列实现生产者-消费者模式,考虑线程间的通讯,会非常麻烦。
参考资料:
聊聊并发(六)ConcurrentLinkedQueue的实现原理分析
Java多线程:队列与阻塞队列的更多相关文章
- Java多线程-新特征-阻塞队列ArrayBlockingQueue
阻塞队列是Java5线程新特征中的内容,Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素 ...
- java多线程8:阻塞队列与Fork/Join框架
队列(Queue),是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的. BlockingQueue 而阻塞队列BlockingQueue除了继承 ...
- java多线程系列10 阻塞队列模拟
接下来的几篇博客会介绍下juc包下的相关数据结构 包含queue,list,map等 这篇文章主要模拟下阻塞队列. 下面是代码 import java.util.LinkedList; import ...
- JAVA多线程(二) 并发队列和阻塞队列
github代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-brian-query-service/ ...
- java并发编程学习: 阻塞队列 使用 及 实现原理
队列(Queue)与栈(Stack)是数据结构中的二种常用结构,队列的特点是先进先出(First In First Out),而Stack是先进后出(First In Last Out),说得通俗点: ...
- Java并发编程:阻塞队列(转载)
Java并发编程:阻塞队列 在前面几篇文章中,我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList), ...
- 【转】Java并发编程:阻塞队列
在前面几篇文章中,我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList),这些工具都为我们编写多线程程 ...
- Java并发编程:阻塞队列 <转>
在前面几篇文章中,我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList),这些工具都为我们编写多线程程 ...
- 12、Java并发编程:阻塞队列
Java并发编程:阻塞队列 在前面几篇文章中,我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList), ...
- (转)Java并发编程:阻塞队列
Java并发编程:阻塞队列 在前面几篇文章中,我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList), ...
随机推荐
- 奶瓶beini系统
奶瓶(beini)这个系统,是一款基于 Tiny Core Linux 搭建的无线网络安全测试系统,当然由于它是用来安全测试的系统,因此在安全方面自然有着强大的功能.而且,这个系统非常简便易学,因此现 ...
- WIN2003+IIS6+FastCGI+PHP5.3的安装配置
本文所用的软件的下载地址 fastcgi的下载地址:http://download.microsoft.com/download/E/0/C/E0C0709A-66E5-4113-9A6C-A5F65 ...
- com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database 'smvch'
1.错误描述 INFO:2015-05-01 14:20:44[main] - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDat ...
- dijit.byId("grid") is undefined
1.错误描述 TypeError:dijit.byId(...) is undefined (68 out of range 3) 2.错误原因 var gridName = dijit ...
- C#利用 string.Join 泛型集合快速转换拼接字符串
C#利用 string.Join 泛型集合快速转换拼接字符串 List<int> superior_list = new List<int>(); superior_list. ...
- 三:Linux 的基本命令、
Ubuntu切换用户 su root sudo passwd root 使用管理员提权修改root 登录密码 连续输入两次即可..... 重置root 用户密码 例:当前登录用户为:ubuntu,但是 ...
- Directory Opus(DO) 个人使用经验 1.0
设置语言为中文 即时过滤器 设置好之后,在文件目录直接先点击“ ; ”键,然后就可以即时过滤了. 自带的图片查看器查看图片时适应窗口 设置默认窗口 将当前打开的窗口配置为默认窗口,以后每次重新打开DO ...
- java——基础语法
java基础语法 1.关键字:java赋予特殊含义的单词. 2.标识符:程序中开发人员自定义的名词,例如:类名,函数名,变量名(注意事项:①不能以阿拉伯数字开头②不能采用关键字). 3.常量:固定的数 ...
- [Baltic2004]数字序列
原题请见<左偏树的特点及其应用>BY 广东省中山市第一中学 黄源河 题意 给出序列\(a[1...n]\),要求构造序列\(b[1...n]\)使得\(\sum_{i=1}^{n}|a_i ...
- HAproxy负载均衡
HAproxy 简介 HAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案. HAProxy特别适用于那些负载特大的web站点,这 ...