快速掌握并发编程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】即可免费获取
王学兵带儿子买菜,大肚腩双下巴胡子拉碴不修边幅,儿子和诺一拼演技近日,有媒体爆出王学兵携儿子出门买菜的照片。照片中王学兵戴着黑色棒球帽和黑色边框眼镜,身穿黑色T恤和黑色裤子,儿子骑着自行车走在前面,他跟随在儿子后面。从近处看能看到王学兵最近长胖
漫谈JODHPUR马裤焦特布尔与短靴的由来JODHPUR马裤焦特布尔与短靴的由来JODHPURS焦特布尔位于印度最大的邦RAJASTHAN拉贾斯坦的西部,RAJAPUTRA拉杰普特人的首领RaoJodha在1459年建立焦
花朵盛开季节一起游玩不得不说,从古到今,出游赏花都是不可缺少的休闲娱乐活动,赏花确实是一件非常有益健康的活动。花是春天的信号,是美好的象征,没有人会拒绝它的存在,一年四季春为首,春天赏花因此也最是普通
赵丽蓉出演西游记,全靠搭档念台词,杨洁导演您演错了还有人不知道赵丽蓉老师曾演过西游记吗?当初杨洁导演准备拍摄斗法降三怪这一集的时候,里面有位车迟国王后,杨洁导演觉得这个角色非赵丽蓉老师不可。在邀请她之前,杨洁导演十分忐忑,因为当时
徐霞客游记之粤西游日记二徐霞客是兼具脚踏实地不浪漫情怀的行者。行走欣赏探究沿途的风景。对他。已不能仅仅弼结为爱好。已然是他存在的一种都式。终身旅行亲自考察。不只是科学探索也不是一般的寄情山水。更不是纯属奇
Linux与windows共享文件的神器samba一什么是samba?搭建Samba服务器是为了实现Linux共享目录之后,在Windows可以直接访问该共享目录。现在介绍如何在ubuntu16。04系统中搭建Samba服务。二s
春风十里,花下等你金展春暖花开时节,常会想起岭南节度使崔护的诗句去年今日此门中,人面桃花相应红。年少不懂诗中意。最初,常会去猜想结局会如何?于是,翻阅书籍查找资料,急切想找到答案。对于春天,自然多了
NBA西部决赛第三场勇士胜独行侠5月22日,勇士队球员库里在投中三分球后庆祝。新华社美联当日,在20212022赛季NBA季后赛西部决赛第三场比赛中,金州勇士队客场以109比100战胜达拉斯独行侠队,总比分3比0
推倒重建!CBA劲旅痛下决心外援主帅涉及10余人,41分天才来了CBA休赛期,没有精彩的赛事,对于大家来说,可能百无聊赖,偶尔去刷刷自己关心的球队,有没有最新的动态。而对于球队来说,休赛期才是最为关键的时刻。整个赛季,球队的成绩是好还是坏,这个
东决G3,热火三军用命防守果然G3一开场,热火就展示了强硬的防守,人盯人,并且遇到对方突破,就整体收缩防线,移动积极。抢下后场篮板,快速推进,转换进攻,打对方的立足未稳,第一个进球就是斯特鲁斯的斜四十五
大巴黎是不是太残忍?三大功臣先后被逼走,内马尔或许是下一个大巴黎是不是太残忍?三大功臣先后被逼走,内马尔或许是下一个!根据巴黎圣日耳曼官网5月21日消息,当日法甲迎来202122赛季的收官战,已经提前获得法甲冠军的巴黎圣日耳曼队主场以50