Java并发:阻塞队列BlockingQueue实现原理分析

Object produce() { … }

}

class Consumer implements Runnable{

private final BlockingQueue queue;

Consumer(BlockingQueue q){

queue = q;

}

public void run(){

try{

while(true) {

consume(queue.take())); // 阻塞式获取

}

}catch(InterruptedException ex){ …handle… }

}

void consume(Object x) { … }

}

class Setup{

void main(){

BlockingQueue q = new SomeQueueImplementation();

Producer p = new Producer(q);

Consumer c1 = new Consumer(q);

Consumer c2 = new Consumer(q);

new Thread§.start();

new Thread(c1).start();

new Thread(c2).start();

}

}

阻塞队列提供的方法

=============

BlockingQueue 对插入操作、移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用:

方法类别抛出异常返回特殊值一直阻塞超时退出插入add(e)offer(e)put(e)offer(e, time, unit)移除remove()poll()take()poll(time, unit)瞅一瞅element()peek()

博主在这边大概解释一下,如果队列可用时,上面的几种方法其实效果都差不多,但是当队列空或满时,会表现出部分差异:

  1. 抛出异常:当队列满时,如果再往队列里add插入元素e时,会抛出IllegalStateException: Queue full的异常,如果队空时,往队列中取出元素【移除或瞅一瞅】会抛出NoSuchElementException异常。

  2. 返回特殊值:队列满时,offer插入失败返回false。队列空时,poll取出元素失败返回null,而不是抛出异常。

  3. 一直阻塞:当队列满时,put试图插入元素,将会一直阻塞插入的生产者线程,同理,队列为空时,如果消费者线程从队列里take获取元素,也会阻塞,知道队列不为空。

  4. 超时退出:可以理解为一直阻塞情况的超时版本,线程阻塞一段时间,会自动退出阻塞。

我们本篇的重点是阻塞队列,那么【一直阻塞】和【超时退出】相关的方法是我们分析的重头啦。

阻塞队列的七种实现

=========

Java并发:阻塞队列BlockingQueue实现原理分析

  • ArrayBlockingQueue:由数组构成的有界阻塞队列。

  • LinkedBlockingQueue:由链表构成的界限可选的阻塞队列,如不指定边界,则为Integer.MAX_VALUE。

  • PriorityBlockingQueue:支持优先级排序【类似于PriorityQueue的排序规则】的无界阻塞队列。

  • DelayQueue:支持延迟获取元素的无界阻塞队列。

  • SynchronousQueue:不存储元素的阻塞队列,每个插入的操作必须等待另一个线程进行相应的删除操作,反之亦然。

另外BlockingQueue有两个继承子接口,分别是:TransferQueue和BlockingDeque,他们有各自的实现类:

  • LinkedTransferQueue:由链表组成的无界TransferQueue

  • LinkedBlockingDeque:由链表构成的界限可选的双端阻塞队列,如不指定边界,则为Integer.MAX_VALUE。

BlockingDeque比较好理解一些,支持双端操作嘛,TransferQueue又是个啥玩意呢?

TransferQueue和BlockingQueue的区别

==================================

BlockingQueue:当生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素但队列为空时,消费者会被阻塞。

TransferQueue则更进一步,生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。新添加的transfer方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程transfer到另一个线程的过程中,它有效地实现了元素在线程之间的传递(以建立Java内存模型中的happens-before关系的方式)。

并发编程网: Java 7中的TransferQueue

1、ArrayBlockingQueue

========================

ArrayBlockingQueue是由数组构成的有界阻塞队列,支持FIFO的次序对元素进行排序。

这是一个典型的有界缓冲结构,可指定大小存储元素,供生产线程插入,供消费线程获取,但注意,容量一旦指定,便不可修改。

队列空时尝试take操作和队列满时尝试put操作都会阻塞执行操作的线程。

该类还支持可供选择的公平性策略,ReentrantLock可重入锁实现,默认采用非公平策略,当队列可用时,阻塞的线程都可以争夺访问队列的资格。

// 创建采取公平策略且规定容量为10 的ArrayBlockingQueue

ArrayBlockingQueue queue = new ArrayBlockingQueue<>(10, true);

2、LinkedBlockingQueue

=========================

LinkedBlockingQueue是由链表构成的界限可选的阻塞队列,如不指定边界,则为Integer.MAX_VALUE,因此如不指定边界,一般来说,插入的时候都会成功。

LinkedBlockingQueue支持FIFO先进先出的次序对元素进行排序。

public LinkedBlockingQueue() {

this(Integer.MAX_VALUE);

}

public LinkedBlockingQueue(int capacity) {

if (capacity <= 0) throw new IllegalArgumentException();

this.capacity = capacity;

last = head = new Node(null);

}

3、PriorityBlockingQueue

===========================

PriorityBlockingQueue是一个支持优先级的无界阻塞队列,基于数组的二叉堆,其实就是线程安全的PriorityQueue。

默认情况下元素采取自然顺序升序排列,也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。

需要注意的是如果两个对象的优先级相同(compare 方法返回 0),此队列并不保证它们之间的顺序。

PriorityBlocking可以传入一个初始容量,其实也就是底层数组的最小容量,之后会使用grow扩容。

// 这里传入10是初始容量,之后会扩容啊,无界的~ , 后面参数可以传入比较规则,可以用lambda表达式哦

PriorityQueue priorityQueue = new PriorityQueue<>(10, new Comparator() {

@Override

public int compare (Integer o1, Integer o2) {

return 0;

}

});

4、DelayQueue

================

DelayQueue是一个支持延时获取元素的无界阻塞队列,使用PriorityQueue来存储元素。

队中的元素必须实现Delayed接口【Delay接口又继承了Comparable,需要实现compareTo方法】,每个元素都需要指明过期时间,通过getDelay(unit)获取元素剩余时间【剩余时间 = 到期时间 - 当前时间】。

当从队列获取元素时,只有过期的元素才会出队列。

static class DelayedElement implements Delayed {

private final long delayTime; // 延迟时间

private final long expire; // 到期时间

private final String taskName; // 任务名称

public DelayedElement (long delayTime, String taskName) {

this.delayTime = delayTime;

this.taskName = taskName;

expire = now() + delayTime;

}

// 获取当前时间

final long now () {

return System.currentTimeMillis();

}

// 剩余时间 = 到期时间 - 当前时间

@Override

public long getDelay (TimeUnit unit) {

return unit.convert(expire - now(), TimeUnit.MILLISECONDS);

}

// 靠前的元素是最快过期的元素

@Override

public int compareTo (Delayed o) {

return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));

}

}

5、SynchronousQueue

======================

SynchronousQueue是一个不存储元素的阻塞队列,每个插入的操作必须等待另一个线程进行相应的删除操作,反之亦然,因此这里的Synchronous指的是读线程和写线程需要同步,一个读线程匹配一个写线程。

你不能在该队列中使用peek方法,因为peek是只读取不移除,不符合该队列特性,该队列不存储任何元素,数据必须从某个写线程交给某个读线程,而不是在队列中等待倍消费,非常适合传递性场景。

SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。

该类还支持可供选择的公平性策略,默认采用非公平策略,当队列可用时,阻塞的线程都可以争夺访问队列的资格。

public SynchronousQueue() {

this(false);

}

// 公平策略使用TransferQueue 实现, 非公平策略使用TransferStack 实现

public SynchronousQueue(boolean fair) {

transferer = fair ? new TransferQueue() : new TransferStack();

}

6、LinkedTransferQueue

=========================

LinkedTransferQueue是由链表组成的无界TransferQueue,相对于其他阻塞队列,多了tryTransfer和transfer方法。

TransferQueue:生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。新添加的transfer方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程transfer到另一个线程的过程中,它有效地实现了元素在线程之间的传递(以建立Java内存模型中的happens-before关系的方式)。

7、LinkedBlockingDeque

=========================

LinkedBlockingDeque是由链表构成的界限可选的双端阻塞队列,支持从两端插入和移除元素,如不指定边界,则为Integer.MAX_VALUE。

Java并发:阻塞队列BlockingQueue实现原理分析

阻塞队列的实现机制

=============

本文不会过于详尽地解析每个阻塞队列源码实现,但会总结通用的阻塞队列的实现机制。

以阻塞队列接口BlockingQueue为例,我们以其中新增的阻塞相关的两个方法为主要解析对象,put和take方法。

  • put:如果队列已满,生产者线程便一直阻塞,直到队列不满。

  • take:如果队列已空,消费者线程便开始阻塞,直到队列非空。

其实我们之前在学习Condition的时候已经透露过一些内容,这里利用ReentrantLock实现锁语义,通过锁关联的condition条件队列来灵活地实现等待通知机制。

之前已经详细地学习过:Java并发包源码学习系列:详解Condition条件队列、signal和await

public ArrayBlockingQueue(int capacity, boolean fair) {

if (capacity <= 0)

throw new IllegalArgumentException();

this.items = new Object[capacity];

// 初始化ReentrantLock

lock = new ReentrantLock(fair);

// 创建条件对象

notEmpty = lock.newCondition();

notFull = lock.newCondition();

}

put方法

=========

public void put(E e) throws InterruptedException {

// 不能加null 啊

checkNotNull(e);

final ReentrantLock lock = this.lock;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)**

img

写在最后

可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

[外链图片转存中…(img-q295p9tL-1713446146085)]

[外链图片转存中…(img-BjCH2CaK-1713446146088)]

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值