飞利浦·斯塔克|简单总结阻塞队列ArrayBlockingQueue源码

飞利浦·斯塔克|简单总结阻塞队列ArrayBlockingQueue源码

文章图片

飞利浦·斯塔克|简单总结阻塞队列ArrayBlockingQueue源码

文章图片


前面Java并发这块的锁、并发工具、原子类、线程池都已经学习完了 , 接下来是队列的学习 。
ArrayBlockingQueue总结先直接总结ArrayBlockingQueue相关的特性再根据源码来进行说明 , 它的主要特性如下:
1、他是一个由数组实现的FIFO有界阻塞队列 , 数组由final修饰;
2、ArrayBlockingQueue有界且固定 , 在构造函数时必须指定大小 , 确认后不支持改变(确定数组且不可变);
3、在多线程环境下不保证“公平性”;
4、通过ReentrantLock与Condition实现线程安全;
重要属性介绍主要属性如下图:

ArrayBlockingQueue的属性还是比较简单 , 首先是存放数据的Object数组 , 它是final修饰的 , 所以队列的长度不可变 。
然后三个int属性分别表示下次获取数据时应该从数组的哪里获取 , 下次保存数据时应该保存到数组的哪里 , count记录着还有多少个可以拿 。
所有方法首先都必须获取到lock的锁 , lock有公平与非公平锁 , 默认实现的是非公平锁 , 也可以在初始化ArrayBlockingQueue指定 , 所以默认ArrayBlockingQueue并不保证公平性 。
notEmpty与notFull都是通过lock创建 , 都是在初始化ArrayBlockingQueue是初始化出来 。
这里简单介绍了属性的作用 , 接下来会通过源码再来理解它的作用 。
最关键的两个私有方法首先要说两个私有方法 , 应该队列主要的方法最后都依赖这两个私有方法 , 直接看源码如下图:

【飞利浦·斯塔克|简单总结阻塞队列ArrayBlockingQueue源码】enqueue方法用来把数据保存到数组items中 , 会递增putIndex , 也就是下次应该保存的位置 , 如果putIndex等于了数组的长度 , 则下次为0 。
最后会唤醒那些调用notEmpty.await()阻塞的线程 , 实际上只有take方法调用了 。
dequeue方法是获取数据 , 获取的是takeIndex处的数据 , 在获取过后会把items[takeIndex
处设置为null , 同样递增takeIndex后如果等于了数组的长度则会被置为0 。
最后会唤醒那些调用notFull.await()阻塞的线程 , 只有put方法调用了 。
所以主要的变化在于putIndex和takeIndex , 这里总结了他们有如下4种关系:

如果不加保护他们不止这些关系 , 比如takeIndex比putIndex跑的快 , 那么就会获取到null值 , 获取putIndex比takeIndex多走一个轮回还多那么就会出现数据被覆盖 , 造成数据丢失 。
只有保证它们是这几种关系才能保证数据的安全性 , 避免数据被覆盖或者获取到null值 , 并且实现了FIFO先进先出 , 而队列提供的公共方法都必须要保证这种安全 。
添加数据方法介绍ArrayBlockingQueue提供了4个添加方法add、offer、offer(指定阻塞时间)、put;
add方法调用的是offer方法 , 而offer方法先获取到锁 , 再判断队列是否已满 , 已满直接返回false , 没有满则调用enqueue保存数据 。 所以add与offer没有阻塞 。
offer还有一个可以在队列已满情况下阻塞指定时间(timeout)在尝试保存的方法 , 在判断队列已满的情况会调用”nanos = notFull.awaitNanos(nanos);”阻塞线程 , 一定时间后如果还是满的则会返回false 。
put获取到锁 , 如果数组已满则调用notFull.await一直阻塞等待唤醒 , 唤醒后再次验证是否已满 , 没满则调用enqueue , 否则再次阻塞 。 所以向队列中加数据只有put方法才支持真正的阻塞 , 保证添加成功 , 其他方法会可能保存失败 。
获取数据方法介绍获取数据提供了poll、poll(指定阻塞时间)、take、peek
poll方法先获取到锁 , 如果count==0则返回null否则调用dequeue方法获取结果 。
poll(指定阻塞时间)在判断count==0时会先阻塞执行时间 , 然后再次判断 , 如果还是等于0则返回null如果不等于0则调用dequeue方法获取结果 。
take方法先获取到锁然后在循环判断count是否等于0如果等于0则调用notEmpty.await()阻塞 , 否则调用dequeue获取结果 , 同样take方法也会保证一定能拿到数据 , 否则会一直阻塞 。
peek是偷看的意思 , peek方法在获取到锁后直接获取item[takeIndex
的元素返回 , 然后不做任何事情 , 就好像偷偷看下下一次会获取哪一个元素 , 但是不影响队列 。
总结ArrayBlockingQueue还有一些其他次要的方法 , 就不再一一说明了 , ArrayBlockingQueue通过两个索引来保存和拉取数据 , 并且实现了先进先出的特性 。 虽然数组的长度是固定有限的 , 但是通过循环使用数组 , 也能通过他同步无限的数据 。
分析方法发现只有take方法和put方法才能保证真正获取或者保存数据成功 , 如果没有成功则会一直阻塞 , 其他方法则不一定 。
Java程序员日常学习笔记 , 如理解有误欢迎各位交流讨论!


    推荐阅读