【JUC】JDK1.8源碼分析之ConcurrentLinkedQueue(五)

一、前言

  接著前面的分析,接下來分析ConcurrentLinkedQueue,ConcurerntLinkedQueue一個基于鏈接節點的無界線程安全隊列。此隊列按照 FIFO(先進先出)原則對元素進行排序。隊列的頭部是隊列中時間最長的元素。隊列的尾部 是隊列中時間最短的元素。新的元素插入到隊列的尾部,隊列獲取操作從隊列頭部獲得元素。當多個線程共享訪問一個公共 collection 時,ConcurrentLinkedQueue是一個恰當的選擇。此隊列不允許使用null元素。

二、ConcurrentLinkedQueue數據結構

  通過源碼分析可知,ConcurrentLinkedQueue的數據結構與LinkedBlockingQueue的數據結構相同,都是使用的鏈表結構。ConcurrentLinkedQueue的數據結構如下

  說明:ConcurrentLinkedQueue采用的鏈表結構,并且包含有一個頭結點和一個尾結點。

三、ConcurrentLinkedQueue源碼分析

  3.1 類的繼承關系

public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>implements Queue<E>, java.io.Serializable {}

  說明:ConcurrentLinkedQueue繼承了抽象類AbstractQueue,AbstractQueue定義了對隊列的基本操作;同時實現了Queue接口,Queue定義了對隊列的基本操作,同時,還實現了Serializable接口,表示可以被序列化。

  3.2 類的內部類 

    private static class Node<E> {// 元素volatile E item;// next域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的值UNSAFE.putObject(this, itemOffset, item);}// 比較并替換item值boolean casItem(E cmp, E val) {return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);}void lazySetNext(Node<E> val) {// 設置next域的值,并不會保證修改對其他線程立即可見UNSAFE.putOrderedObject(this, nextOffset, val);}// 比較并替換next域的值boolean casNext(Node<E> cmp, Node<E> val) {return 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);}}}
View Code

  說明:Node類表示鏈表結點,用于存放元素,包含item域和next域,item域表示元素,next域表示下一個結點,其利用反射機制和CAS機制來更新item域和next域,保證原子性。

  3.3 類的屬性  

public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>implements Queue<E>, java.io.Serializable {// 版本序列號        private static final long serialVersionUID = 196745693267521676L;// 反射機制private static final sun.misc.Unsafe UNSAFE;// head域的偏移量private static final long headOffset;// tail域的偏移量private static final long tailOffset;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 transient volatile Node<E> head;// 尾結點private transient volatile Node<E> tail;
}
View Code

  說明:屬性中包含了head域和tail域,表示鏈表的頭結點和尾結點,同時,ConcurrentLinkedQueue也使用了反射機制和CAS機制來更新頭結點和尾結點,保證原子性。

  3.4 類的構造函數

  1. ConcurrentLinkedQueue()型構造函數  

    public ConcurrentLinkedQueue() {// 初始化頭結點與尾結點head = tail = new Node<E>(null);}
View Code

  說明:該構造函數用于創建一個最初為空的 ConcurrentLinkedQueue,頭結點與尾結點指向同一個結點,該結點的item域為null,next域也為null。

  2. ConcurrentLinkedQueue(Collection<? extends E>)型構造函數  

    public ConcurrentLinkedQueue(Collection<? extends E> c) {Node<E> h = null, t = null;for (E e : c) { // 遍歷c集合// 保證元素不為空
            checkNotNull(e);// 新生一個結點Node<E> newNode = new Node<E>(e);if (h == null) // 頭結點為null// 賦值頭結點與尾結點h = t = newNode;else {// 直接頭結點的next域
                t.lazySetNext(newNode);// 重新賦值頭結點t = newNode;}}if (h == null) // 頭結點為null// 新生頭結點與尾結點h = t = new Node<E>(null);// 賦值頭結點head = h;// 賦值尾結點tail = t;}
View Code

  說明:該構造函數用于創建一個最初包含給定 collection 元素的 ConcurrentLinkedQueue,按照此 collection 迭代器的遍歷順序來添加元素。

  3.5 核心函數分析

  1. offer函數  

    public boolean offer(E e) {// 元素不為null
        checkNotNull(e);// 新生一個結點final Node<E> newNode = new Node<E>(e);for (Node<E> t = tail, p = t;;) { // 無限循環// q為p結點的下一個結點Node<E> q = p.next;if (q == null) { // q結點為null// p is last nodeif (p.casNext(null, newNode)) { // 比較并進行替換p結點的next域// Successful CAS is the linearization point// for e to become an element of this queue,// and for newNode to become "live".if (p != t) // p不等于t結點,不一致    // hop two nodes at a time// 比較并替換尾結點casTail(t, newNode);  // Failure is OK.// 返回return true;}// Lost CAS race to another thread; re-read next
            }else if (p == q) // p結點等于q結點// We have fallen off list.  If tail is unchanged, it// will also be off-list, in which case we need to// jump to head, from which all live nodes are always// reachable.  Else the new tail is a better bet.// 原來的尾結點與現在的尾結點是否相等,若相等,則p賦值為head,否則,賦值為現在的尾結點p = (t != (t = tail)) ? t : head;else// Check for tail updates after two hops.// 重新賦值p結點p = (p != t && t != (t = tail)) ? t : q;}}
View Code

  說明:offer函數用于將指定元素插入此隊列的尾部。下面模擬offer函數的操作,隊列狀態的變化(假設單線程添加元素,連續添加10、20兩個元素)。

  ① 若ConcurrentLinkedQueue的初始狀態如上圖所示,即隊列為空。單線程添加元素,此時,添加元素10,則狀態如下所示

  ② 如上圖所示,添加元素10后,tail沒有變化,還是指向之前的結點,繼續添加元素20,則狀態如下所示

  ③ 如上圖所示,添加元素20后,tail指向了最新添加的結點。

  2. poll函數  

    public E poll() {restartFromHead:for (;;) { // 無限循環for (Node<E> h = head, p = h, q;;) { // 保存頭結點// item項E item = p.item;if (item != null && p.casItem(item, null)) { // item不為null并且比較并替換item成功// Successful CAS is the linearization point// for item to be removed from this queue.if (p != h) // p不等于h    // hop two nodes at a time// 更新頭結點updateHead(h, ((q = p.next) != null) ? q : p); // 返回itemreturn item;}else if ((q = p.next) == null) { // q結點為null// 更新頭結點
                    updateHead(h, p);return null;}else if (p == q) // p等于q// 繼續循環continue restartFromHead;else// p賦值為qp = q;}}}
View Code

  說明:此函數用于獲取并移除此隊列的頭,如果此隊列為空,則返回null。下面模擬poll函數的操作,隊列狀態的變化(假設單線程操作,狀態為之前offer10、20后的狀態,poll兩次)。

  ① 隊列初始狀態如上圖所示,在poll操作后,隊列的狀態如下圖所示

  ② 如上圖可知,poll操作后,head改變了,并且head所指向的結點的item變為了null。再進行一次poll操作,隊列的狀態如下圖所示。

  ③ 如上圖可知,poll操作后,head結點沒有變化,只是指示的結點的item域變成了null。

  3. remove函數  

    public boolean remove(Object o) {// 元素為null,返回if (o == null) return false;Node<E> pred = null;for (Node<E> p = first(); p != null; p = succ(p)) { // 獲取第一個存活的結點// 第一個存活結點的item值E item = p.item;if (item != null &&o.equals(item) &&p.casItem(item, null)) { // 找到item相等的結點,并且將該結點的item設置為null// p的后繼結點Node<E> next = succ(p);if (pred != null && next != null) // pred不為null并且next不為null// 比較并替換next域
                    pred.casNext(p, next);return true;}// pred賦值為ppred = p;}return false;}
View Code

  說明:此函數用于從隊列中移除指定元素的單個實例(如果存在)。其中,會調用到first函數和succ函數,first函數的源碼如下  

    Node<E> first() {restartFromHead:for (;;) { // 無限循環,確保成功for (Node<E> h = head, p = h, q;;) {// p結點的item域是否為nullboolean hasItem = (p.item != null);if (hasItem || (q = p.next) == null) { // item不為null或者next域為null// 更新頭結點
                    updateHead(h, p);// 返回結點return hasItem ? p : null;}else if (p == q) // p等于q// 繼續從頭結點開始continue restartFromHead;else// p賦值為qp = q;}}}
View Code

  說明:first函數用于找到鏈表中第一個存活的結點。succ函數源碼如下  

    final Node<E> succ(Node<E> p) {// p結點的next域Node<E> next = p.next;// 如果next域為自身,則返回頭結點,否則,返回nextreturn (p == next) ? head : next;}
View Code

  說明:succ用于獲取結點的下一個結點。如果結點的next域指向自身,則返回head頭結點,否則,返回next結點。下面模擬remove函數的操作,隊列狀態的變化(假設單線程操作,狀態為之前offer10、20后的狀態,執行remove(10)、remove(20)操作)。

  ① 如上圖所示,為ConcurrentLinkedQueue的初始狀態,remove(10)后的狀態如下圖所示

  ② 如上圖所示,當執行remove(10)后,head指向了head結點之前指向的結點的下一個結點,并且head結點的item域置為null。繼續執行remove(20),狀態如下圖所示

  ③ 如上圖所示,執行remove(20)后,head與tail指向同一個結點,item域為null。

  4. size函數  

    public int size() {// 計數int count = 0;for (Node<E> p = first(); p != null; p = succ(p)) // 從第一個存活的結點開始往后遍歷if (p.item != null) // 結點的item域不為null// Collection.size() spec says to max outif (++count == Integer.MAX_VALUE) // 增加計數,若達到最大值,則跳出循環break;// 返回大小return count;}
View Code

  說明:此函數用于返回ConcurrenLinkedQueue的大小,從第一個存活的結點(first)開始,往后遍歷鏈表,當結點的item域不為null時,增加計數,之后返回大小。

五、示例

  下面通過一個示例來了解ConcurrentLinkedQueue的使用  

package com.hust.grid.leesf.collections;import java.util.concurrent.ConcurrentLinkedQueue;class PutThread extends Thread {private ConcurrentLinkedQueue<Integer> clq;public PutThread(ConcurrentLinkedQueue<Integer> clq) {this.clq = clq;}public void run() {for (int i = 0; i < 10; i++) {try {System.out.println("add " + i);clq.add(i);Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}
}class GetThread extends Thread {private ConcurrentLinkedQueue<Integer> clq;public GetThread(ConcurrentLinkedQueue<Integer> clq) {this.clq = clq;}public void run() {for (int i = 0; i < 10; i++) {try {System.out.println("poll " + clq.poll());Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ConcurrentLinkedQueueDemo {public static void main(String[] args) {ConcurrentLinkedQueue<Integer> clq = new ConcurrentLinkedQueue<Integer>();PutThread p1 = new PutThread(clq);GetThread g1 = new GetThread(clq);p1.start();g1.start();}
}
View Code

  運行結果(某一次):  

add 0
poll null
add 1
poll 0
add 2
poll 1
add 3
poll 2
add 4
poll 3
add 5
poll 4
poll 5
add 6
add 7
poll 6
poll 7
add 8
add 9
poll 8
View Code

  說明:GetThread線程不會因為ConcurrentLinkedQueue隊列為空而等待,而是直接返回null,所以當實現隊列不空時,等待時,則需要用戶自己實現等待邏輯。

六、總結

  ConcurrentLinkedQueue的源碼也相對簡單,其實對于并發集合而言,分析源碼時首先理解單線程情況,然后再考慮在多線程并發時的情況,這樣會使得分析源碼容易得多,ConcurrentLinkedQueue和LinkedBlockingQueue的區別還是很明顯的(前者在取元素時,若隊列為空,則返回null;后者會進行等待)。謝謝各位園友的觀看~

轉載于:https://www.cnblogs.com/leesf456/p/5539142.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/257094.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257094.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257094.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學習筆記(10):Python網絡編程并發編程-粘包現象

立即學習:https://edu.csdn.net/course/play/24458/296240?utm_sourceblogtoedu粘包現象&#xff1a;服務器接收到客戶端的命令后&#xff0c;進行執行得到結果后&#xff0c;再發送回給客戶端&#xff0c;在這個過程中如果服務器返回的結果的字節數會大于客戶端所接收最大字節…

某法院HP-P4500存儲數據恢復案例

好久沒出來寫博客了&#xff0c;過年來了一直很忙&#xff0c;尤其是最近&#xff0c;忙著做了好幾個大單子。先是一個醫院50TB的HP-EVA4400&#xff0c;接著是一個法院12TB的HP-P4500&#xff0c;前幾天還有做了一個某游樂城12TB的VMware VMFS虛擬機恢復。雖然忙點&#xff0c…

數組指針與指針數組的區別

1、數組指針 定義&#xff1a;數組指針式一個指向一維數組的指針變量&#xff0c;定義數組指針的格式為&#xff1a; int (*p) [5] 數據類型 &#xff08;*指針名&#xff09; [常量表達式] 數組元素為整形&#xff0c;*p的兩側圓括號不能省略 2、指針數組 定義&#xff1a…

[thinkphp] 是如何輸出一個頁面的

表面上看&#xff0c;TP輸出一個頁面很簡單&#xff1a;$this->display(); 實際上是怎么回事呢&#xff1f;$this->display(); 這個display()方法是定義在ThinkPHP/Library/Think/Controller.class.php這個文件中的 protected function display($templateFile,$charset,$…

關于反射blog

非常好的Java反射例子 瘋狂java 在學習編程的過程中&#xff0c;我覺得不止要獲得課本的知識&#xff0c;更多的是通過學習技術知識提高解決問題的能力&#xff0c;這樣我們才能走在最前方&#xff0c;更多Java學習&#xff0c;請瀏覽瘋狂java官網。Java反射在我們Java學習的…

學習筆記(11):Python網絡編程并發編程-粘包底層原理分析

立即學習:https://edu.csdn.net/course/play/24458/296241?utm_sourceblogtoedu1.send和recv底層分析 1&#xff09;不管是recv還是send都不是直接接收對方數據或者發送給對方數據&#xff0c;而是對自己的操作系統內存進行操作&#xff1b; 2&#xff09;客戶端與服務端并不是…

切面編程(4)

這篇介紹的是最為常見的切面編程首先介紹的是通過注解Aspect來配置AOP類Component Aspect public class Acsep {//定義切入點Pointcut("execution(* com.test.*.*(..))")//切面公式public void aspect(){ }//執行方法之前Before("aspect()")public void be…

c++存儲類型

1、c中的存儲類型一般有靜態存儲、棧、和自動類型三種&#xff0c;一般默認值是為自動類型auto

多線程編程 (1) -NSThread

多線程編程 (1) -NSThread 每個iOS應用程序都有個專門用來更新顯示UI界面、處理用戶觸摸事件的主線程&#xff0c;因此不能將其他太耗時的操作放在主線程中執行&#xff0c;不然會造成主線程堵塞(出現卡機現象)&#xff0c;帶來極壞的用戶體驗。一般的解決方案就是將那些耗時的…

交叉工具鏈的搭建方法(測試成功)

之前安裝了一個rehat6的linux系統&#xff0c;把交叉編譯搭建給忽視了&#xff0c;結果在編譯uboot的時候出現問題&#xff0c;顯示找不到arm-linux-gcc。于是自己來搭建交 叉編譯環境。出現好多錯。先是解壓時沒在后邊加 -C/&#xff0c;后是直接自己創建了個目錄&#xff0c…

VMware內存回收與分配機質

VMware內存回收與分配機質 整理了下學習過的東西&#xff0c;為了防止以后忘記。^_^VMware內存回收按照內存回收先后順充&#xff0c;依次為&#xff1a;1.TPS 透明頁共享2.Ballooning 氣球回收3.Compressiong 內存壓縮4.Swapping 內存交換網上對這個的解釋也挺多&#xff…

學習筆記(12):Python網絡編程并發編程-解決粘包問題-簡單版本

立即學習:https://edu.csdn.net/course/play/24458/296243?utm_sourceblogtoedu 粘包現象的解決&#xff1a;簡單版 1.思路&#xff1a; 在服務器端計算出執行命令后結果的字節長度&#xff0c;然后再將字節數長度send即通知給客戶端&#xff0c;客戶端根據這個字節數的長度一…

關于for循環中的變量int i 如果跳出了這個for循環后,i的值是繼續保留還是被釋放掉了

#include<iostream> using namespace std;int main() {char a[10]; //定義一個一維數組用來存放字符串int i,j; //定義變量cout<<"請輸入字符&#xff1a;“;for(i0;i<10;i) //接收用戶的輸入{ ci…

keil優化等級設置

優化級別說明&#xff08;僅供參考&#xff09;&#xff1a;則其中的 Code Optimization 欄就是用來設置C51的優化級別。共有9個優化級別&#xff08;書上這么寫的&#xff09;&#xff0c;高優化級別中包含了前面所有的優化級別。現將各個級別說明如下&#xff1a;0級優化&…

SVN命令使用詳解

1、檢出svn co http://路徑(目錄或文件的全路徑) [本地目錄全路徑] --username 用戶名 --password 密碼svn co svn://路徑(目錄或文件的全路徑) [本地目錄全路徑] --username 用戶名 --password 密碼svn checkout http://路徑(目錄或文件的全路徑) [本地目錄全路徑]…

服務器排障 之 nginx 499 錯誤的解決

問題描述&#xff1a; Nginx 服務器大量499報錯 220.181.165.136 - - [18/May/2015:10:31:02 0800] "POST /v1/jobsHTTP/1.1" 499 0 "" "bdHttpRequest/1.0.0"115.239.212.7 - - [18/May/2015:10:31:03 0800] "GET /v1/job/643309e3-dc73-4…

二叉查找樹的先序遍歷,中序遍歷,后序遍歷

1、有一個二叉查找樹&#xff0c;存儲者字符A,B,C,D,E,F,G,H,下面哪個結果是后序樹遍歷結果 A. ADBCEGFH B. BCAGEHFD C. BCAEFDHG D. BDACEFHG 我的結題思路是將每個答案按照后序的遍歷方法把二叉樹存儲數據的結構還原&#xff0c;看是否滿足二叉樹的性質。 二叉樹的性…

學習筆記(13):Python網絡編程并發編程-解決粘包問題-終極版本

立即學習:https://edu.csdn.net/course/play/24458/296244?utm_sourceblogtoedu 粘包現象解決&#xff08;終極版&#xff09; 1.簡單版的問題所在 1&#xff09;報頭信息不一定只是包含著命令執行結果的字節數長度&#xff0c;在文件傳輸的時候也可能包含文件名等&#xff0c…

C#多態

C#多態 多態性&#xff08;C# 編程指南&#xff09;轉自MSDN通過繼承&#xff0c;一個類可以用作多種類型&#xff1a;可以用作它自己的類型、任何基類型&#xff0c;或者在實現接口時用作任何接口類型。這稱為多態性。C# 中的每種類型都是多態的。類型可用作它們自己的類型或用…

Ubuntu 14.04.02 安裝openvswitch-2.3.1

Open vSwitch安裝 安裝好操作系統 # lsb_release -a LSB Version: core-2.0-amd64:core-2.0-noarch:core-3.0-amd64:core-3.0-noarch:core-3.1-amd64:core-3.1-noarch:core-3.2-amd64:core-3.2-noarch:core-4.0-amd64:core-4.0-noarch:core-4.1-amd64:core-4.1-noarch:security…