java中的阻塞队列

前言

BlockingQueue是java.util.concurrent包中的重要部分,顾名思义其是阻塞队列,基于ReentrantLock实现。BlockingQueue在多线程中可以实现线程间“传输”数据,如生产者消费者模式。它有众多成员分别有不同的功能和特点,下面进行逐一分析。

核心方法

队列的核心是入队出队,那么阻塞队列也不例外,下表简单列出一些方法包含了入队的三种(add、offer、put),出队的三种(take、poll、remove),还有返回栈顶元素的两种(element、peek)各个方法的特点通过下表简单进行总结。
在这里插入图片描述
在上面的表格中只是列出一些核心方法阻塞队列中还有其他也很重要的方法在此就不都列举,同时在阻塞队列中同一个方法进行重载后会带来不一样的效果,拿offer方法举例:

(1)offer(E e):表示如果可能的话,将e加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false;      
(2)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。

BlockingQueue成员

在这里插入图片描述

ArrayBlockingQueue(FIFO)

ArrayBlockingQueue数据结构是一个定长的环形数组,所以其没有扩容机制,为什么说它是定长的呢?因为其在创建对象的时候就要指定其容量,指定后就不可以再变。同时在其类中有两个整型变量putIndex和takeIndex,putIndex在队尾、takeIndex在队首分别用于入队和出队(先进先出队列)。其内部的锁是Reentrantlock,其存取只有一个锁对象操作数组。在其内部通过定义两个Condition (notFull和notEmpty进行阻塞,定义两个的原因是因为当队列中为空要阻塞取数据的线程——出队用notEmpty阻塞,当队列中满了要阻塞放入数据的线程——入队用notFull阻塞。那么有阻塞就有唤醒,出队时队列为空我们用notEmpty阻塞所以入队时唤醒notEmpty,反之,在出队的时候唤醒notFull。

LinkedBlockingQueue (FIFO)

LinkedBlockingQueue数据结构是一个链表(内部类Node),可以指定容量也可以不指定,当不指定容量时,容量默认等于Integer.MAX_VALUE,其内部有两个Node变量 first和last,分别指向第一个节点和最后一个节点用于出队入队,内部锁也是Reentrantlock,但是与ArrayBlockingQueue不同的是它有两个锁对象(putlock和takelock),实现存取分离,它之所以可以用两个锁对象是因为其内部类Node,在链表上的每个节点都是一个Node对象,并且出队入队分别在操作不同的Node对象,**同样内部也定义了两个Condition(notEmpty和notFull)用于阻塞,putlock定义的是notFull、takelock定义的是notEmpty,**阻塞时和ArrayBlockingQueue原理一样。当删除节点时要将两把锁都加上 。

SynchronousQueue

同步队列没有任何内部容量,其内部支持公平模式和非公平模式(默认是非公平模式),构造函数传入true时是公平模式——先进先出,flase时是非公平模式——先进后出,因此在内部定义了TransferStack和TransferQueue类分别对应着非公平和公平模式。在源码中其存取调用的是同一个方法transfer,只是put时transfer方法的参数数据部分是有值的,take时transfer方法的参数数据部分是null。正是有这一区别才能实现同一个方法既可以存又可以取。无论是公平模式或是非公平模式SynchronousQueue内部原理都是是通过模式匹配(线程的模式)返回数据和唤醒线程的。SynchronousQueue线程的阻塞和唤醒使用的是LockSupport.park和LockSupport.unpark。内部的锁用的是CAS加自旋。

SynchronousQueue其工作大概过程:
1.当线程1向队列中放入数据时(transfer方法数据部分不为空定义为DATA模式然后阻塞线程1,线程2取数据(transfer数据部分为null定义为REQUEST模式,那么此时两个线程模式匹配(DATA与REQUEST)将线程1的数据返回给线程2,然后将线程1唤醒。当线程2也是向队列放入数据那同样定义为DATA模式并阻塞。后面的其他线程同理,只有出现REQUEST模式的时候才进行匹配返回数据唤醒线程。
2.当然还有可能队列中没有DATA模式的线程,开始先来的是取数据的线程(transfer数据部分为null),那么同样定义为REQUEST并阻塞,此时等待DATA模式的线程出现,然后在进行匹配在返回数据和唤醒线程。在队列中不管是存还是取,只要队列中没有出现匹配模式的线程就全部阻塞直到出现相匹配模式的线程再进行唤醒和返回数据。公平模式和非公平模式的不同之处就是返回数据和唤醒线程的时候是否按照先进先出的顺序。
注意:公平和非公平模式是应用在这个阻塞队列的或者说是按照什么顺序返回数据唤醒线程,DATA和REQUEST是应用的线程匹配之间的。

在这里插入图片描述
在这里插入图片描述

LinkedTransferQueue

LinkedTransferQueue数据结构是链表且容量可变。LinkedTransferQueue内部有tryTransfer和transfer方法,所以可以自己控制放元素时是否阻塞。transfer方法和SynchronousQueue的transfer一样会阻塞,而tryTransfer方法不会阻塞,这也是其和SynchronousQueue的不同之处,LinkedTransferQueue取元素的时候必须阻塞。在锁和阻塞方面其和SynchronousQueue是一样都使用的是CAS+自旋 和LockSupport.park等。

PriorityBlockingQueue

优先级阻塞队列中数据结构使用数组+二叉堆,可以指定容量且该阻塞队列会自动扩容最大扩容到Integer.MAX_VALUE - 8,默认容量为11。其内部使用Reentrantlock,存取使用一把锁,同样内部也定义了一个Condition(notEmpty)用于当队列为空的时出队阻塞,只定义一个Condition的原因是其会自动扩容所以认为它的容量一直是Integer.MAX_VALUE - 8,相当于无界不会放满,所以入队不用阻塞。因为其数据结构中使用了堆,所以在其入队时会进行堆化,出队时将堆顶出队,然后重新堆化。堆顶存储的就是优先级最大的,该队列总是优先级高的先出队。

LinkedBlockingDeque

链表双端阻塞队列的数据结构同LinkedBlockingQueue,其容量可以指定,不指定默认为Integer.MAX_VALUE(同LinkedBlockingQueue),在锁和阻塞方面和ArrayBlockingQueue相同,唯一的特点就是其入队出队在队尾队首都可以进行(双端操作)。同时它比LinkedBlockingQueue多了一些API(功能)。

DelayQueue

延迟阻塞队列内部使用PriorityQueue这个队列,所以也相当于一个优先级队列,PriorityQueue队列和上面PriorityBlockingQueue的区别就是PriorityQueue没有提供阻塞功能。其容量也相当于无界,锁使用的Reentrantlock,内部定义了一个Condition( available)用于出队时阻塞。其入队不会阻塞,因为其容量相当于无界所以入队不用阻塞。出队时存在以下两种情况。

1.出队时当队列为空,那就用 available阻塞,这种阻塞的唤醒在入队时唤醒。
2.当出队时队列不为空,则会首先检查堆顶元素的过期时间,如果过期时间到了那么就取出元素,如果过期时间还有没到,那么就会判断leader线程是否为空,不为空直接阻塞,为空将当前线程置为leader线程然后按照过期时间阻塞,这种阻塞的唤醒是按照过期时间唤醒,到时间自动唤醒。
leader线程:是DelayQueue的一个全局变量,用来记录当队列不为空时当前是否已经存在线程被阻塞。如果有,那么它所记录的线程就是应该最先被唤醒的线程。

那么为什么要有leader线程呢?
假设:没有这个leader线程
条件:现在有线程1过期时间为30s,线程2过期时间15s,线程1的优先级大于线程2,初始时间是0s
1.线程1要出队,此时距离过期时间还有30s,那么其会阻塞等待到期时间(30s);
2.线程1在等待了5s后,线程2也要出队,同样其会阻塞等待到期时间(15s);
3.当时间来到20s时,线程2时间到了直接唤醒,就可以出队了,而线程1还在阻塞着,那么此时就会有一个问题,线程1优先级高,线程1应该先出队,但是现在却是线程2先出队,那么就破环了优先级。
那么我们加入这个leader线程后呢? leader为空。
1.线程1要出队,此时距离过期时间还有30s,那么其会先判断leader是否为空,为空后将自己置为leader然后阻塞等待到期时间(30s);
2.线程1在等待了5s后,线程2也要出队,同样其会判断leader是否为空,不为空,那么就直接阻塞,注意此时不是按照过期时间阻塞。
3.当时间来到20s时,线程2因为不是用过期时间阻塞的所以还要继续阻塞,等到时间到达30s,此时线程1唤醒出队,此时leader为空那么线程2变为leader等待过期时间到达然后出队。这样就保证了优先级。

总结

BlockingQueue不仅实现了一个完整队列所具有的基本功能,同时在多线程环境下,它还自动管理了多线程间的自动等待与唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值