ArrayBlockingQueue源码解读

虽然在某程序员最喜欢的工具调查中,90%的人选择了eclipse,但是我这里还是要强烈推荐idea.我在来我目前公司之前也是用eclipse,但是公司里的程序员用的都是idea,然后自己也尝试转型,一用上以后发现我已经深深爱上它了,真的是太好用了。尤其是读源码和源码debug方面,真的是非常便捷,在这里强烈推荐。有人说这个要收费,我想说呵呵,我自己也是百度的2016破解,可惜没有做记录,目前网上还是有注册服务器的,你需要多一点耐心就好。

好了废话不多说,开搞。今天读的源码的是ArrayBlockingQueue。首先来看一张类接口关系图。


层次关系图

其实这个类主要的方法是在BlockingQuere接口中定义好的。所有的实现除了add方法调用了abstarctQueue之外,其他都是自己实现的。下面我们来看看他是怎么实现的。


类的全局变量


第一个final的object数组即是实现的容器,这里我们要强调一下,如果你了解类的加载过程,那你就对这个一点也不奇怪了,如果不懂你可以去看一下类的加载过程,这里的final没有被static修饰,也就代表着它的初始化是在new对象的Init方法里初始化的。init也就是我们的构造函数执行的过程。

takeindex代表着下一个可取的数组下标。初始化为0,然后调用poll,remove,take方法都会进行++操作,如果>=数组容量长度,则重置为0

putindex代表着下一个可存放的数组下标,初始化为0,如果调用add,offer,put则进行++操作,如果>=数组容量长度,则重置为0

count代表总共存放了多少个对象,

下面维护了一把锁,2个condition,notempty用于维护的是空阻塞,这里比如调用poll和take的时候如果count为0则会进行阻塞,只要添加一个,则会执行notempty.signl操作唤醒阻塞,具体的情况下会讲解。notFull维护的是满阻塞,调用offer和put如果数组满了则会阻塞。

上面这些就组成了我们实现这个队列的所有必要的元素。

当然这里重点讲解的是重点几个API实现的过程。下面我们一个一个的展开讲。

首先我们来看下构造函数,


构造函数

其中第一个需要指定初始化大小,这里的数组大小一旦指定是不可变的。


默认初始化的锁为非公平锁,如果不懂公平锁非公平锁,请自行查看


如果需要指定公平锁,则使用2个参数的构造函数,下面看三个参数的


初始化动作也是调用了2个参数的,但是多了一步,我们可以把别的集合直接转到这个队列中去。

============================下面看方法实现================================

首先看add、put、offer方法。首先说下三个方法的区别:

add,添加失败会抛出异常

put,如果数组满则会阻塞到有空位为止

offer,添加失败会返回false。

下面先来看add方法。


add方法直接调用了abstractQueue中的add方法,


从这里看为什么我们add方法会抛出异常,原来就是这么来的,如果添加失败则抛出异常。添加动作能交给offer去做,添加失败返回false,然后就抛出异常,其实就是对offer的返回形式进行了一次改变而已嘛!

下面我们看offer操作

offer提供了2个操作,一个是只有一个待添加元素的参数,另外一个是多了2个参数,一个是long类型基本数据类型,一个是jdk1.8的新的时间对象工具。不要着急,我们一个个来看。



先来看1个参数的:


一进来首先对它进行null值检测,其实实现很简单,就是如果为null抛出空指针异常。

然后锁定,判断,最终添加操作是在enqueue这个操作里实现的。其实三种添加操作都是通过enqueue方法实现的,所有的取出操作都是dequeue方法实现的。然后如果成功返回true,如果失败返回false.就是这么实现的。


添加操作。

这里的实现很简单,直接放入下一个可存放的位置,然后对下一个可存放下标++操作,然后与数组容量判断,如果满了则置为0,然后对记录总存量的count进行++操作。然后调用非空锁唤醒操作。这里留一个疑问,比如我当前添加满了,然后下一个可存放的位置重置为0,这时候另外一个操作按照下标取走了中间一个位置的对象,或者是末尾位置的对象,这时候数组是有空位置的,这时候我们再存入一个时候会发生什么事情呢

下面我们看待时间参数的offer方法


第一个参数是待存入元素,第二个参数是超时时间,第三个参数是时间单位。

通过上面的while循环可知,如果阻塞超时,则直接返回false,如果阻塞期间,count变化了,则进入下面的enqueue方法存入对象。实现就这么简单。

还剩最后一个我们看put方法,


put方法就是在while循环里阻塞,然后等待唤醒。这里的lock锁是通过lockInterruptibly();进行获取的,这里我写这的时候也不明白为什么这么写。后面会研究下关于锁的获取的不同方式。

这里我们已经把添加元素的方法都看完了,是不是很简单啊。

下面我们看取的几个实现,包括take,poll,peek。

take方法是阻塞获取,如果为空,则阻塞,直到有对象了再获取,从数组中删除该对象

poll方法是如果为空返回null,从数组中删除该对象

peek方法是直接返回该对象,并不会从数组中删除

先来看take的实现方式



很简单,如果为空则阻塞,一旦有元素则唤醒,然后调用dequeue方法获取,我们看看dequeue的实现


这里先把下一个可获取元素位置的下标取出来,然后把这个位置修改为null,然后在进行对可获取下标位置的修改,如果==数组容量则重置为0.然后对非满进行唤醒操作。


poll方法有2个实现,一个是如果为空立即返回null,另外一个就是阻塞等待时间,如果超时立即返回。


用了一个三目运算符,soeasy


这里也会在while循环里进行阻塞,如果超时返回null,如果有新的对象进来,则调用dequeue。

下面看peek方法


peek直接返回了下一个可取位置的对象,不进行删除操作。

到此为止,我们的添加和获取都看过了,下面来看remove方法,有4个方法

remove(o),删除指定对象

remove()删除一个

removeAll删除集合中的全部对象

removeIf()


先来看不带参数的,remove方法直接返回了当前移除的对象,删除的呢正好是下一个可获取的对象。


移除操作实际调用的是abstractQueue中的remove,最后实际调用的还是poll方法。

下面看删除指定对象


这里首先我们找出需要删除对象的下标,通过指定下标取删除

下面看看删除指定位置的,

如果指定删除的下标正好等于可获取的下一个下标,则直接把该位置的修改为null,然后对下一个可获取的下标进行++操作,如果等于数组长度,则重置为0;


如果指定位置的下标不等于下一个可获取位置的下标,则在一个for循环里,从指定删除位置下标开始,把下一个对象前移一位


这里我们假设目前5个大小的数组是满的,下一个可存放下标重置为0了,然后待移除的下标 i 位置为2,则next=2+1,然后把下标为3的移动到下标为2的,然后把 i=next,循环,然后当i=4的时候,我们的next=5与数组容量一样大小了,这时候把next重置于0,总不能把位置0的前移吧,所以这里需要判断是不是等于下一个可存放位置,如果等于,然后直接把 i位置的置于0,然后把 下一个可存的下标设置为i。等等?  这里是不是正好解决了我们前面哪个问题? 如果我删除了中间一个,则会把下一个可存放重置到最后一个位置。


这里需要注意的是,能够让下一个可取下标变化的操作一定是从数组中删除了这个元素的操作,这样也就避免了我们前面提到的问题,即不存在覆盖的问题。

removeAll是在AbstractCollection抽象类里实现的,通过迭代器操作。


还有最后一个removeIf


也是通过迭代器操作,然后多了一个判断条件,也就是条件过滤。这个predicate仅仅是个接口,目前还没有任何实现,可以通过自己来定义实现,需要重写evaluate方法。有兴趣的可以自己查看。

下面再看看我们别的API的实现。


element方法返回下一个可获取的对象,如果为Null抛出异常


contain(1);返回是否包含这个对象,


arr.drainTo(l); 这个方法参数是一个Collection对象,可以吧队列中的数据批量复制到这个集合中去,


第二个参数是复制多少个,如果不设置默认复制全部,然后删除掉queue中对应的对象。这个有没有一点 批量获取的意思呢?


接下图


接上图。

如果指定第二个参数,则会从可获取下标开始然后复制指定个数的对象。

这里如果我们数组容量为5,批量拉取了前3个,然后后面的会不会前移呢?从源码中看,不会,而且没有必要。我们的下一个存下标和下一个取下标会不停的循环,所以队列即使形成了中空,都是没有关系的。之前有一个ringbuffer的设计,大家可以百度搜的看一看,而且disruptor的ringbuffer设计的更为巧妙。

clear()清空所有


arr.size();返回实际存放的大小


remainingCapacity()返回剩余多少可用容量

arr.toArray();返回一个对象数组


arr.toArray(new Integer[10]);和上面的区别就是如果不指定则返回一个object[]数组,如果指定则返回到你指定的数组中。


arr.spliterator(); 这个是返回一个spliterator,我还没有学习过这个,所以这里暂时不介绍。

因为是基于数组实现的,所以这个的优点不必说,存入和取出都非常快,唯一慢的一个地方就是指定删除。如果数组特别大,比如有1000个容量,然后我们要删除第2个对象,然后就需要把后面的全部前移一位。所以对于有这种需要的需要慎用。

还有一点就是不能扩容,因此初始化时候必须要根据业务初始化为合适大小的队列。

使用场景不必说:

对于快速存快速取,不存在删除中间的需求来说是非常合适的。


如又错误,以及意见建议,欢迎有缘看帖者能够给出批评指正,不甚感激。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,473评论 0 3
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,196评论 0 4
  • ArrayBlockingQueue是一个有界的阻塞队列,所有的元素按照先入先出(FIFO)的规则进队出队,在队列...
    辣公公阅读 385评论 0 0
  • 考察了一下自称为“most popular”前端framework:bootstrap。初步认为,并不适用于我们的...
    NoteCode阅读 368评论 1 1
  • 如果世界上有种药吃了以后可以回到从前某一刻,让你对某件事情重新做一次选择,你吃不吃? 我猜很多人倾家荡产...
    星暖KK阅读 620评论 2 7