ConcurrentLinkedQueue源码解读

上一篇介绍了ArrayBlockingQueue的源码,这节我们介绍它的兄弟,基于链表的实现,直接开看

head见名知意:这里必须要说的是一个好的名字,真的能够给阅读者带来不一样的便利。我读过的源码,是深有体会的。也就是我们链表的头

tail同样我们可以知道是尾巴节点,二者都是一个Node类型,我们看看这个类的定义


只有2个属性,一个是泛型,一个是指向下一个节点的next。

下面是构造函数,一个带集合对象参数的构造函数,一个无参构造函数

如果是无参的直接把头尾都初始化为null,

如果是有参数的,则如下图,遍历我们传进来的集合,然后取出每一个对象,并且包装为node对象,然后遍历并且调用node的lazySetNext执行链条指针设置,通过名字可以理解为是一个延迟设置,并且把t永远指向最新加入的一个。最后然后把head和tail指向了h和t。

我们下面看看lazySetNext的方法实现

这里直接调用了UNSAFE对象的putorderedObject方法,三个参数分别是当前node,偏移量,下一个node对象。从下图中可知,node在初始化的时候根据node类的类对象通过反射获取了next这个属性,我们node类的next属性就是指向的下一个node,然后通过UNSAFE的objectFieldOffset设置为偏移量。这里可以理解为,每次我创建node对象的时候,通过反射机制,返回一个新的node,然后设置为属性偏移量。有兴趣的同学可以去读下反射的这些源码,反射返回的是一个复制的新对象。也就是说我们每次初始化的时候已经对node里面的nextNode通过反射也进行了初始化。

这里不知道大家注意到没,我们并没有设置边界,也就是说很可能链表队列是一个无边界的。

这里提出个问题:队列的大小怎么维护的,目前为止我们还没有看到过队列大小的属性值。

下面我们看add方法,

通过解读可以知道,是没有边界的,所以add方法永远会成功

这里调用了offer方法,我们来看看offer实现

这里假设我们第一次添加一个节点1,然后此时tail为null,然后p.next为null,然后用cas改变head为节点1,tail的next改变为指向自己

接下来我们添加第二个节点2,此时tail为null,t为null,p为null,p.next为自身,显然q不为null,然后q==p,然后通过三目运算符把p指向了节点1.

过程如下图:


后面的继续追加基本就是这个模式了,这里用了个for循环,在并发下保证能追加进去。

这里有一个问题我也搞不清楚,从后面的节点debug查看,CAS算法的确是把next指向了新入的节点,但是当第一个节点null的时候,用CAS产生的效果是,head指向了新加入节点,tail的next指向了自身。我一直也没搞明白,然后在加入节点后把这个Null的节点丢弃掉。至此,无锁的linkedqueue的追加就完成了。

下面我们来看poll方法,poll方法总是弹出第一个,也就是先进先出。从这里可以看得出在并发下,如果产生竞争,也是通过不停的修改head的for循环来进行弹出的。这里p.casitem后同时会把下一个节点升级为head。这里如果多线程环境下,当一个线程拿到了某一个节点a,第二个线程也拿到了a,但是抢先去拿走了值,此时a节点的下一个节点为null,这时候head也为Null,然后p.next为null,h为null,所以直接返回了null值。如果P.next指向了自身,则满足第三个判断,然后这时候跳过本次循环从最上面开始再次进入循环。如果q=p.next不为null,p也不和q相等,则直接把p指向next,然后再次循环。这样就基本解决了所有的可能性:

             需要弹出的p节点不为Null

             需要弹出的p节点为null,但是下个节点不为null,但指向自己

             需要弹出的p节点为null,但是下个节点不为null,但也不指向自己

             需要弹出的节点为null,下一个也为null,然后返回null。

peek操作总返回head,但是不从链接上删除,这里的判断条件为,当前节点的item如果不为null,则直接对自身进行一个uphead,如果当前节点的item为null,则继续比价当前节点的下一个节点是不是null,如果是null,则进行一个伪head替换,然后返回Null,如果不是则比较当前节点与当前节点的下一个节点是不是相等,如果相等则跳过本次循环继续从头开始,如果不相等则把当前节点指向下个节点,然后再次循环。

isempty方法是拥来查看是否为空

调用的是first,方法,这里面进行了比较判断,也是基于两个for嵌套循环,这个给我们对于并发下实现无锁模型算法提供了非常好的思路。如果当前节点的item不为null,进行一个伪head替换,然后返回当前节点,如果为null,则查看下一个节点是不是null,如果是null,则进行一个伪升级,然后返回null,如果下一个节点不为null,则考虑是不是指向自身,如果是则重新开始循环,如果不是,则把当前节点指向下一个然后继续循环。

size方法,前面我们注意到了,在这个queue中并未维护关于大小的数字,我们接下来看size怎么实现,这里是在size方法里面进行统计的,在原文注释中解释这个返回值并不准确,并发条件下的追加和删除都可能使得返回的size不正确。

contains(o)方法返回是否包含此对象,由方法可以看出,通过遍历这个链条来比较,直到当前的p为null为止。并发条件下仅仅可以作为判断是否曾经存在过,也许在返回true的那一刻,就被消费掉了。这种状态仅仅保证存在过,不保证下一刻存在。

remove方法需要指定删除,也是需要遍历整个链条,如果找到了则立马把当前的item修改为null,然后获取到下一个节点,然后用前一个指向下一个完成删除。这里的删除分2步,第一步首先把item修改为Null,第二步然后修改前一个链条和下一个链条的关系完成删除。

toArray返回一个Object数组,但是对链条中的节点不进行删除操作

toArray数组返回到奥指定的数组中,从源码中可以看到,能返回指定的数组

addAll方法可以直接批量添加一个集合,首先对集合遍历,然后把每一个对象包装为node,然后先将这个集合进行自身链条化,而不是一个个先追加到原来的链条上,这样既能保证我们集合的连续性,同时也避免了并发条件下带来的复杂性。

当上面的链条完成后,我们开始往我们真正的链条上追加,可以看到如果tail的next为null,则直接把我们前面链条的第一个然后连接到next即可,然后把tail指向最后一个,如果失败后先把t指向tail然后把最后一个的next指向null,再进行一次tail的改变。


至此我们整个源码都结束了,由此引出来的2个问题

         第一:如果CAS时,需要修改的对象为null的node,则我们会把目标对象变为我们的head,然后把null的node的next指向自身

        第二:在我们poll的时候,也可以看出这一点来,如果当前节点为null,则直接把目标对象变为我们的head.这里不知道是CAS的算法,还是java自身对CAS的进一步优化呢?

整个LinkedBlockQueue是无锁的,全部都基于for循环和CAS算法,而且也是无界的。

关于这个的优势就是真的快,无锁并发下,通过不停的循环来追加,会很快,因此也会比较耗内存和CPU,因为如果不对请求线程限制,同一时间会有很多线程在不停的抢cpu资源,他们不存在线程终结的概念,只有线程执行完。

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

推荐阅读更多精彩内容