电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Java并發編程之ConcurrentLinkedQueue源碼詳解

瀏覽:3日期:2022-08-12 15:21:34
目錄一、ConcurrentLinkedQueue介紹二、構造方法三、入隊 四、出隊五、總結一、ConcurrentLinkedQueue介紹

并編程中,一般需要用到安全的隊列,如果要自己實現安全隊列,可以使用2種方式:方式1:加鎖,這種實現方式就是我們常說的阻塞隊列。方式2:使用循環CAS算法實現,這種方式實現隊列稱之為非阻塞隊列。從點到面, 下面我們來看下非阻塞隊列經典實現類:ConcurrentLinkedQueue (JDK1.8版)

ConcurrentLinkedQueue 是一個基于鏈接節點的無界線程安全的隊列。當我們添加一個元素的時候,它會添加到隊列的尾部,當我們獲取一個元素時,它會返回隊列頭部的元素。它采用了“wait-free”算法來實現,用CAS實現了非阻塞的線程安全隊列。當多個線程共享訪問一個公共 collection 時,ConcurrentLinkedQueue 是一個恰當的選擇。此隊列不允許使用 null 元素,因為移除元素時實際是將節點中item置為null,如果元素本身為null,則跟刪除有沖突

我們首先看一下ConcurrentLinkedQueue的類圖結構先,好有一個內部邏輯有一個大概的印象,如下圖所示:

Java并發編程之ConcurrentLinkedQueue源碼詳解

主要屬性head節點,tail節點

// 鏈表頭節點private transient volatile Node<E> head;// 鏈表尾節點private transient volatile Node<E> tail;

主要內部類Node

類Node在static方法里獲取到item和next的內存偏移量,之后通過casItem和casNext更改item值和next節點

private static class Node<E> { volatile E item; volatile Node<E> next; /** * Constructs a new node. Uses relaxed write because item can * only be seen after publication via casNext. */ Node(E item) {//將item存放在本節點的itemOffset偏移量位置的內存里UNSAFE.putObject(this, itemOffset, item);//設置this對象的itemoffset位置 } //更新item值 boolean casItem(E cmp, E val) { //this對象的itemoffset位置存放的值如果和期望值cmp相等,則替換為valreturn UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } void lazySetNext(Node<E> val) { //this對象的nextOffset位置存入valUNSAFE.putOrderedObject(this, nextOffset, val); } //更新next節點值 boolean casNext(Node<E> cmp, Node<E> val) { //this對象的nextOffset位置存放的值如果和期望值cmp相等,則替換為valreturn UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; //當前節點存放的item的內存偏移量 private static final long itemOffset; //當前節點的next節點的內存偏移量 private static final long nextOffset; static {try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = Node.class; itemOffset = UNSAFE.objectFieldOffset(k.getDeclaredField('item')); nextOffset = UNSAFE.objectFieldOffset(k.getDeclaredField('next'));} catch (Exception e) { throw new Error(e);} }}

concurrentlinkedqueue同樣在static方法里獲取到head和tail的內存偏移量:之后通過casHead和casTail更改head節點和tail節點值

static { try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> k = ConcurrentLinkedQueue.class;headOffset = UNSAFE.objectFieldOffset (k.getDeclaredField('head'));tailOffset = UNSAFE.objectFieldOffset (k.getDeclaredField('tail')); } catch (Exception e) {throw new Error(e); }} private boolean casTail(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);} private boolean casHead(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);}二、構造方法 無參構造函數,head=tail=new Node<E>(null)=空節點(里面無item值) 集合構造函數(集合中每個元素不能為null):就是將集合中的元素挨個鏈起來

//無參構造函數,head=tail=new Node<E>(null)=空節點//初始一個為空的ConcurrentLinkedQueue,此時head和tail都指向一個item為null的節點public ConcurrentLinkedQueue() { // 初始化頭尾節點 head = tail = new Node<E>(null);} //集合構造函數:就是將集合中的元素挨個鏈起來public ConcurrentLinkedQueue(Collection<? extends E> c) { Node<E> h = null, t = null; for (E e : c) {checkNotNull(e);Node<E> newNode = new Node<E>(e);if (h == null) h = t = newNode;else { t.lazySetNext(newNode);//可以理解為一種懶加載, 將t的next值設置為newNode t = newNode;} } if (h == null)h = t = new Node<E>(null); head = h; tail = t;} private static void checkNotNull(Object v) { if (v == null)throw new NullPointerException();} //putObjectVolatile的內存非立即可見版本,//寫后結果并不會被其他線程看到,通常是幾納秒后被其他線程看到,這個時間比較短,所以代價可以接收void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val);}三、入隊

獲取到當前尾節點p=tail:

如果p.next=null,代表是真正的尾節點,將新節點鏈入p.next=newNode。此時檢查tail是否還是p,如果不是p了,此時更新tail為最新的newNode(只有在tail節點后面tail.next成功添加的元素才不需要更新tail,其實更新不更新tail是交替的,即每添加倆次更新一次tail)。 如果p.next=p,此時其實是p.next==p==null,此時代表p被刪除了,此時需要從新的tail節點檢查,如果此時tail節點還是原來的tail(原來的tail在p前面,肯定也被刪除了),那就只能從head節點開始遍歷了 如果p.next!=null,代表有別的線程搶先添加元素了,此時需要繼續p=p.next遍歷獲取是null的節點(此時需要如果tail變了就使用新的tail往后遍歷)

public boolean offer (E e){ //先檢查元素是否為null,是null則拋出異常 不是null,則構造新節點準備入隊 checkNotNull(e); final Node<E> newNode = new Node<E>(e); //初始p指針和t指針都指向尾節點,p指針用來向隊列后面推移,t指針用來判斷尾節點是否改變 Node<E> t = tail, p = t; for (; ; ) {Node<E> q = p.next;if (q == null) {//p.next為null,則代表p為尾節點,則將p.next指向新節點 // p is last node if (p.casNext(null, newNode)) {/** * 如果p!=t,即p向后推移了,t沒動,則此時同時將tail更新 * 不符合條件不更新tail,這里可以看出并不是每入隊一個節點都會更新tail的 * 而此時真正的尾節點其實是newNode了,所以tail不一定是真正的尾節點, * tail的更新具有滯后性,這樣設計提高了入隊的效率,不用每入隊一個,更新一次 *尾節點 */if (p != t) casTail(t, newNode); // Failure is OK.return true; } // Lost CAS race to another thread; re-read next} else if (p == q)/** * 如果p.next和p相等,這種情況是出隊時的一種哨兵節點代表已被遺棄刪除, * 那就是有線程在一直刪除節點,刪除到了p.next 那此時如果有線程已經更新了tail,那就從p指向tail再開始繼續像后推移 * 如果始終沒有線程更新tail,則p指針從head開始向后推移 * * p從head開始推移的原因:tail沒有更新,以前的tail肯定在哨兵節點的前面(因為此循環是從tail向后推移到哨兵節點的), * 而head節點一定在哨兵節點的后面(出隊時只有更新了head節點,才會把前面部分的某個節點置為哨兵節點) * 此時其實是一種tail在head之前,但實際上tail已經無用了,哨兵之前的節點都無用了, * 等著其他線程入隊時更新尾節點tail,此時的tail才有用所以從head開始,從head開始可以找到任何節點 * */ p = (t != (t = tail)) ? t : head;else/** * p.next和p不相等時,此時p應該向后推移到p.next,即p=p.next, * 如果next一直不為null一直定位不到尾節點,會一直next, * 但是中間會優先判斷tail是否已更新,如果tail已更新則p直接從tail向后推移即可。就沒必要一直next了。 */ // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; }}四、出隊

poll出隊:獲取到當前頭節點p=head:如果成功設置了item為null,即p.catItem(item,null),如果此時被其他線程搶走消費了,此時需要p=p.next,向后繼續爭搶消費,直到成功執行p.catItem(item,null),此時檢查p是不是head節點,如果不是更新p.next為頭結點

public E poll() { restartFromHead: for (;;) {// p節點表示首節點,即需要出隊的節點for (Node<E> h = head, p = h, q;;) { E item = p.item; // 如果p節點的元素不為null,則通過CAS來設置p節點引用的元素為null,如果成功則返回p節點的元素 if (item != null && p.casItem(item, null)) {// Successful CAS is the linearization point// for item to be removed from this queue.// 如果p != h,則更新headif (p != h) // hop two nodes at a time updateHead(h, ((q = p.next) != null) ? q : p);return item; } // 如果頭節點的元素為空或頭節點發生了變化,這說明頭節點已經被另外一個線程修改了。 // 那么獲取p節點的下一個節點,如果p節點的下一節點為null,則表明隊列已經空了 else if ((q = p.next) == null) {// 更新頭結點updateHead(h, p);return null; } // p == q,則使用新的head重新開始 else if (p == q)continue restartFromHead; // 如果下一個元素不為空,則將頭節點的下一個節點設置成頭節點 elsep = q;} }}五、總結

offer:

找到尾節點,將新節點鏈入到尾節點后面,tail.next=newNode,

由于多線程操作,所以拿到p=tail后cas操作執行p.next=newNode可能由于被其他線程搶去而執行不成功,此時需要p=p.next向后遍歷,直到找到p.next=null的目標節點。繼續嘗試向其后面添加元素,添加成功后檢查p是否是tail,如果不是tail,則更新tail=p,添加不成功繼續向后next遍歷

poll:

獲取到當前頭節點p=head:如果成功設置了item為null,即p.catItem(item,null),

如果此時被其他線程搶走消費了,此時需要p=p.next,向后繼續爭搶消費,直到成功執行p.catItem(item,null),此時檢查p是不是head節點,如果不是更新頭結點head=p.next(因為p已經刪除了)

更新tail和head:

不是每次添加都更新tail,而是間隔一次更新一次(head也是一樣道理):第一個搶到的線程拿到tail執行成功tail.next=newNode1此時不更新tail,那么第二個線程再執行成功添加p.next=newNode2會判斷出p是newNode1而不是tail,所以就更新tail為newNode2。

tail節點不總是最后一個,head節點不總是第一個設計初衷:

讓tail節點永遠作為隊列的尾節點,這樣實現代碼量非常少,而且邏輯非常清楚和易懂。但是這么做有個缺點就是每次都需要使用循環CAS更新tail節點。如果能減少CAS更新tail節點的次數,就能提高入隊的效率。

在JDK 1.7的實現中,doug lea使用hops變量來控制并減少tail節點的更新頻率,并不是每次節點入隊后都將 tail節點更新成尾節點,而是當tail節點和尾節點的距離大于等于常量HOPS的值(默認等于1)時才更新tail節點,tail和尾節點的距離越長使用CAS更新tail節點的次數就會越少,但是距離越長帶來的負面效果就是每次入隊時定位尾節點的時間就越長,因為循環體需要多循環一次來定位出尾節點,但是這樣仍然能提高入隊的效率,因為從本質上來看它通過增加對volatile變量的讀操作來減少了對volatile變量的寫操作,而對volatile變量的寫操作開銷要遠遠大于讀操作,所以入隊效率會有所提升。

在JDK 1.8的實現中,tail的更新時機是通過p和t是否相等來判斷的,其實現結果和JDK 1.7相同,即當tail節點和尾節點的距離大于等于1時,更新tail。

到此這篇關于Java并發編程之ConcurrentLinkedQueue源碼詳解的文章就介紹到這了,更多相關Java ConcurrentLinkedQueue源碼內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
主站蜘蛛池模板: 雄松华章(广州华章MBA)官网-专注MBA/MPA/MPAcc/MEM辅导培训 | 踏板力计,制动仪,非接触多功能速度仪,逆反射系数测试仪-创宇 | 尊享蟹太太美味,大闸蟹礼卡|礼券|礼盒在线预订-蟹太太官网 | elisa试剂盒价格-酶联免疫试剂盒-猪elisa试剂盒-上海恒远生物科技有限公司 | 天空彩票天下彩,天空彩天空彩票免费资料,天空彩票与你同行开奖,天下彩正版资料大全 | 阀门智能定位器_电液动执行器_气动执行机构-赫尔法流体技术(北京)有限公司 | 水厂自动化-水厂控制系统-泵站自动化|控制系统-闸门自动化控制-济南华通中控科技有限公司 | 便携式谷丙转氨酶检测仪|华图生物科技百科 | 金属波纹补偿器厂家_不锈钢膨胀节价格_非金属伸缩节定制-庆达补偿器 | 企小优-企业数字化转型服务商_网络推广_网络推广公司 | 【直乐】河北石家庄脊柱侧弯医院_治疗椎间盘突出哪家医院好_骨科脊柱外科专业医院_治疗抽动症/关节病骨伤权威医院|排行-直乐矫形中医医院 | nalgene洗瓶,nalgene量筒,nalgene窄口瓶,nalgene放水口大瓶,浙江省nalgene代理-杭州雷琪实验器材有限公司 | 加中寰球移民官网-美国移民公司,移民机构,移民中介,移民咨询,投资移民 | 雪花制冰机(实验室雪花制冰机)百科 | 长沙中央空调维修,中央空调清洗维保,空气能热水工程,价格,公司就找维小保-湖南维小保环保科技有限公司 | 东莞ERP软件_广州云ERP_中山ERP_台湾工厂erp系统-广东顺景软件科技有限公司 | 出国劳务公司_正规派遣公司[严海]| 单机除尘器 骨架-脉冲除尘器设备生产厂家-润天环保设备 | 武汉不干胶印刷_标签设计印刷_不干胶标签印刷厂 - 武汉不干胶标签印刷厂家 | 机床导轨_导轨板_滚轮导轨-上海旻佑精密机械有限公司 | 包头市鑫枫装饰有限公司| 台式低速离心机-脱泡离心机-菌种摇床-常州市万丰仪器制造有限公司 | 船用烟火信号弹-CCS防汛救生圈-船用救生抛绳器(海威救生设备) | 深圳成考网-深圳成人高考报名网| 座椅式升降机_无障碍升降平台_残疾人升降平台-南京明顺机械设备有限公司 | 包塑软管|金属软管|包塑金属软管-闵彬管业| 小港信息港-鹤壁信息港 鹤壁老百姓便民生活信息网站 | 神马影院-实时更新秒播| 石家庄小程序开发_小程序开发公司_APP开发_网站制作-石家庄乘航网络科技有限公司 | 重庆轻质隔墙板-重庆安吉升科技有限公司| 微波萃取合成仪-电热消解器价格-北京安合美诚科学仪器有限公司 | 闪蒸干燥机-喷雾干燥机-带式干燥机-桨叶干燥机-[常州佳一干燥设备] | 微信聊天记录恢复_手机短信删除怎么恢复_通讯录恢复软件下载-快易数据恢复 | 新车测评网_网罗汽车评测资讯_汽车评测门户报道 | 天坛家具官网| 万师讲师网-优质讲师培训师供应商,讲师认证,找讲师来万师 | 硬齿面减速机_厂家-山东安吉富传动设备股份有限公司 | 地图标注|微信高德百度地图标注|地图标记-做地图[ZuoMap.com] | 广州中央空调回收,二手中央空调回收,旧空调回收,制冷设备回收,冷气机组回收公司-广州益夫制冷设备回收公司 | 创富网-B2B网站|供求信息网|b2b平台|专业电子商务网站 | 无线遥控更衣吊篮_IC卡更衣吊篮_电动更衣吊篮配件_煤矿更衣吊篮-力得电子 |