快速掌握并发编程ArrayBlockingQueue底层原理和实战java
背景
在JDK1.5 的时候,在新增的 Concurrent 包中,BlockingQueue 很好的解决了多线程中,如何高效安全"传输"数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。
本文详细介绍了BlockingQueue 家庭中的所有成员,包括他们各自的功能以及常见使用场景。分析ArrayBlockingQueue 的底层源码分析。
BlockingQueue 简介
阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示:
从上图我们可以很清楚看到,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出。
常用的队列主要有以下两种:(当然通过不同的实现方式,还可以延伸出很多不同类型的队列,DelayQueue 就是其中的一种)
先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性。
后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。
下面两幅图演示了BlockingQueue 的两个常见阻塞场景:
如上图所示:当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
这也是我们在多线程环境下,为什么需要BlockingQueue 的原因。作为BlockingQueue 的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue 都给你一手包办了。
既然BlockingQueue 如此神通广大,让我们一起来见识下它的常用方法:
BlockingQueue 的核心方法
添加数据:
offer(anObject):表示如果可能的话,将 anObject 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false。(本方法不阻塞当前执行方法的线程)
offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入 BlockingQueue,则返回失败。
put(anObject):把 anObject 加到 BlockingQueue 里,如果 BlockingQueue 没有空间,则调用此方法的线程被阻断,直到 BlockingQueue 里面有空间再继续。
获取数据:
poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,取不到时返回 null。
poll(long timeout, TimeUnit unit):从 BlockingQueue 取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 BlockingQueue 有新的数据被加入。
drainTo():一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个数), 通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
常见 BlockingQueue
在了解了 BlockingQueue 的基本功能后,让我们来看看BlockingQueue 家庭大致有哪些成员?
ArrayBlockingQueue 源码分析
构造方法
在ArrayBlockingQueue 中有三个构造方法,常用这两种,另外的这里就不说了,没多意义。
//创造一个队列,指定队列容量就可以了,默认模式为非公平模式 public ArrayBlockingQueue(int capacity) { this(capacity, false); } ////创造一个队列,指定队列容量和指定公平性 public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; //这里用的就是重入锁, //公平性通过fair来判断 lock = new ReentrantLock(fair); //不为空条件队列,明显就是给消费者用 notEmpty = lock.newCondition(); //没有满条件队列,给生产者用 notFull = lock.newCondition(); }
复制代码
重要属性
//底层数据结构,说明底层存储数据的数据结构是数组 private final E[] items; //用来为下一个take/poll/remove的索引(出队) private int takeIndex; //用来为下一个put/offer/add的索引(入队) private int putIndex; //队列中元素的个数 private int count; /** Main lock guarding all access */ private final ReentrantLock lock;//锁 /** Condition for waiting takes */ private final Condition notEmpty;//等待出队的条件 /** Condition for waiting puts */ private final Condition notFull;//等待入队的条件
复制代码
从这几个属性就能看出来,ArrayBlockingQueue 的底层组成为:
数组一个重入锁两个条件队列
入队
add()方法
public boolean add(E e) { // 调用父类的add(e)方法 return super.add(e); } public boolean add(E e) { // 调用offer(e)如果成功返回true,如果失败抛出异常 if (offer(e)) return true; else throw new IllegalStateException("Queue full"); }
复制代码
offer(e)方法入队
入参 e 为 null,会抛空指针异常
元素插入到队尾(putIndex 自增)
唤醒线程
生产者消费者模式
/** * 在队尾插入一个元素, * 如果队列没满,立即返回true; * 如果队列满了,立即返回false * 注意:该方法通常优于add(),因为add()失败直接抛异常 */ public boolean offer(E e) { //检查存入的数据是否为null checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { //如果数组满了,返回入队失败 if (count == items.length) return false; else { //入队 enqueue(e); return true; } } finally { lock.unlock(); } } //如果存入的数据是null的会抛控指针异常 private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); } //putIndex是int类型,默认值就是0 private void enqueue(E x) { final Object[] items = this.items; //从下标为0的位置开始放入数据 items[putIndex] = x; //如果putIndex等于数组大小,证明这是最后一个能存放的,然后把putIndex设置为0 //否则++putIndex if (++putIndex == items.length){ putIndex = 0; } //放入数据个数+1 count++; /** * 唤醒一个线程 * 如果有任意一个线程正在等待这个条件,那么选中其中的一个区唤醒。 * 在从等待状态被唤醒之前,被选中的线程必须重新获得锁 */ notEmpty.signal(); }
复制代码
带超时时间的 offer,在队尾插入一个元素,,如果数组已满,则进入等待,直到出现以下三种情况:-->阻塞
被唤醒
等待时间超时
当前线程被中断
put 方法入队
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(); } }
复制代码
小结
add(e)时如果队列满了则抛出异常;offer(e)时如果队列满了则返回 false;put(e)时如果队列满了则使用 notFull 等待;offer(e, timeout, unit)时如果队列满了则等待一段时间后如果队列依然满就返回 false;enqueue()利用放指针循环使用数组来存储元素;
出队
take 方法出队
从头部出队
队中没有数据,等待被唤醒再取数据。
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //加锁,如果线程中断了抛出异常 lock.lockInterruptibly(); try { //队列中不存元素 while (count == 0){ /* * 一直等待条件notEmpty,即被其他线程唤醒 * (唤醒其实就是,有线程将一个元素入队了,然后调用notEmpty.signal() * 唤醒其他等待这个条件的线程,同时队列也不空了) */ notEmpty.await(); } //否则出队 return dequeue(); } finally { lock.unlock(); } } private E dequeue() { final Object[] items = this.items; // 取取指针位置的元素 E x = (E) items[takeIndex]; // 把取指针位置设为null items[takeIndex] = null; // 取指针前移,如果数组到头了就返回数组前端循环利用 if (++takeIndex == items.length) takeIndex = 0; // 元素数量减1 count--; if (itrs != null) itrs.elementDequeued(); // 唤醒notFull条件 notFull.signal(); return x; }
复制代码
poll 方法出队
太简单了,如果队列里没有数据,就直接返回 null,否则从队列头部出队
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { //如果队列里没有数据就直接返回null //否则从队列头部出队 return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } }
复制代码
remove 方法
public E remove() { // 调用poll()方法出队 E x = poll(); if (x != null) // 如果有元素出队就返回这个元素 return x; else // 如果没有元素出队就抛出异常 throw new NoSuchElementException(); }
复制代码
小结
remove()时如果队列为空则抛出异常;poll()时如果队列为空则返回 null;take()时如果队列为空则阻塞等待在条件 notEmpty 上;poll(timeout, unit)时如果队列为空则阻塞等待一段时间后如果还为空就返回 null;dequeue()利用取指针循环从数组中取元素;
小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【111】即可免费获取
声震天下,势吞金乌DC评兴戈APT7日蚀又称日食,一种天文现象,相传佛教释迦摩尼的一位弟子之母,生性暴戾。玉帝得知,将其打入十八层地狱,变为恶狗,永世不得超生。弟子日夜修炼,成了地藏菩萨。为救母亲,他用锡杖打开地狱门
真无线耳机也能有箱子味?DC评惠威HiViAW76对于玩过音响的朋友们来说,惠威这个品牌一定不陌生。作为民族企业,经过30年的发展,惠威已经成为世界著名高级音响制造公司,并以各类杰出的电声产品享誉业界。而近年来,惠威也顺应时代发展
如期而至,很有精神耳机美学直击2020中国广州国际耳机展(图Envied文EnviedDC)今年首个如期举办的大型HiFi盛会2020中国(广州)国际耳机展终于在万众期盼之中于9月1213日在白云国际会议中心圆满举办了。今年的展会耳机美
钢铁之心DC评水月雨SSR超级银船如果你想买个入门价位的耳机,你会买什么?这几年有粉丝求推荐入门耳机,我的答案往往是水月雨。原因很简单,水月雨是一个拥有广泛群众基础的品牌,对于这样的品牌,除非它想砸自己招牌,否则它
神说,要有光DC评水月雨Illumination光神说要有光就有了光。圣经以这一句话开启。上帝也曾说我是世界的光。跟从我的,就不在黑暗里行走,必要得着生命的光。光是希望公义良善荣耀。有了光,才有这世间万物,才有这芸芸众生。水月雨的
直男情书DC评LZA7和老忠初次见面是在一年半以前我搞的线下聚会上,印象中的老忠就是一个内敛的中年大哥,一如他的产品般朴实无华。他创立的LZHIFI在国内一直可谓默默无闻,甚至有的烧友都没有听说过,但是
如期而至,很有精神耳机美学直击2020中国国际耳机展中篇413锦锋音响北京安润在展厅门口的原创入门新品OPAQ2。1成为大家试听较多的对象。这是一台兼顾桌面发烧系统和小型无源音箱系统的一体化解码耳放功放一体机,在成都和上海展上备受关注,
动听音符,动人频率DC评7Hzi88致命频率第一次听到七赫兹7Hz这个品牌的时候,我是有一丝诧异的。为什么呢,是因为众所周知,人耳能听到的频率范围是20Hz至20000Hz之间,所以,7Hz这个频响人耳是根本听不到的
如期而至,很有精神耳机美学直击2020中国国际耳机展下篇C厅展位C01享声SOUNDAWAREC02NFAudio宁梵声学NM2的声音十分的正,理性之中又包含着感性,并不会显得人声冷冰冰。NM2的外观十分的好看,如同声音一样的清澈透明。
1113DC叔评海贝R2FD1Beans海贝R2FD1Beans应该是我玩过的最亲民的官方西装三件套了。官方套装最大的意义就在于从设计之初就根据彼此做好了调音匹配和优化,能最大程度上发挥出整套设备的实力,另外统一的设计语
HTC开发元宇宙平台XR企业当红齐天获数亿元人民币融资(VRPinea10月14日讯)今日重点新闻HTC目前正在开发一款名叫ViveportVerse的元宇宙平台小米建银国际领投,XR企业当红齐天完成B轮数亿元融资乌克兰元宇宙虚拟活动