CAS:Compare and Swap 比較並交換
java.util.concurrent包完全建立在CAS之上的,沒有CAS就沒有並發包。並發包借助了CAS無鎖算法實現了區別於synchronized同步鎖的樂觀鎖。因為對於CAS算法來說,就是在不加鎖的前提下而假設沒有沖突去完成某個操作,如果因為沖突而導致操作失敗,那么就進行重試,直到成功為止。
CAS有三個操作數:真實的內存值V、預期的內存值A、要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為新值B,否則什么都不做。
我們通過原子操作類AtomicInteger來研究下在沒有加鎖的前提下是如何做到數據正確性的:
private volatile int value;
通過關鍵字volatile保證value值在線程間是可見的,這樣在獲取value值的時候可以直接獲取:
public final int get() { return value; }
我們來看看++i是怎么做到的:
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
1、其中expect是預期的內存值A,而update是要修改的值B,this就是真實的內存值V
2、這里采用了CAS操作,每次從內存中讀取數據然后將此數據+1后的結果進行CAS操作,如果成功就返回結果,否則重試,直到成功。
3、compareAndSet利用JNI來完成CPU指令的操作,該方法的過程類似如下:
if(this==expect) { this=update; return true; } else { return false; }
這里成功的過程也不是原子操作,有比較this==expect與this=update這兩步操作,這兩步的原子性的保證是由底層硬件支持的。
CAS的缺點
雖然CAS有效的解決了原子操作的問題,但是其仍然有三個劣勢:
1、ABA問題:因為CAS需要在操作前檢查下值有沒有發生變化,如果沒有則更新。但是如果一個值開始的時候是A,變成了B,又變成了A,那么使用CAS進行檢查的時候會發現它的值沒有發生變化,但是事實卻不是如此。
ABA問題的解決思路是使用版本號,如A-B-A變成1A-2B-3A
2、循環時間長開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
3、只能保證一個共享變量的原子操作:對一個共享變成可以使用CAS進行原子操作,但是多個共享變量的原子操作就無法使用CAS,這個時候只能使用鎖。
ConcurrentLinkedQueue
在JAVA多線程應用中,隊列的使用率很高,多數生產者和消費者的首選數據結構就是隊列(先進先出)。JAVA提供的線程安全隊列分為阻塞隊列和非阻塞隊列,其中阻塞隊列的典型例子就是BlockingQueue,而非阻塞隊列的典型例子就是ConcurrentLinkedQueue,在實際應用中要根據實際需要來選取。
使用阻塞算法的隊列可以用一個鎖(入隊和出隊用同一把鎖)或兩個鎖(入隊和出隊用不同的鎖)等方式來實現;非阻塞的實現方式則可以使用循環CAS的方式來實現。
ConcurrentLinkedQueue是一個不限制大小的非阻塞隊列,保存了當前鏈表的頭指針head和尾指針tail。每個節點Node由節點元素item和指向下一個節點的引用next組成。節點之間通過next關聯起來,從而組成一張鏈表結構的隊列。鏈表中最后加入的節點稱為尾節點。
private transient volatile Node<E> head = new Node<E>(null, null); /** Pointer to last node on list **/ private transient volatile Node<E> tail = head;
1、頭指針head不允許為空,數據內容永遠是null。鏈表的第一個有效元素是最早入隊的元素,即head.next。
2、尾指針tail並不一定指向尾指針,所以兩者之間還是有區別的。
入隊列
入隊列就是將入隊節點添加到隊列的尾部

第一步:添加元素1,隊列更新head的next節點為元素1節點,因為tail節點默認情況下等於head節點,所以tail的next節點也指向元素1節點。
第二步:添加元素2,隊列更新元素1節點的next節點為元素2節點,然后tail指向元素2節點。
第三步:添加元素3,然后tail的next節點指向元素3節點。
第四步:添加元素4,隊列更新元素3節點的next節點為元素4節點,然后tail節點指向元素4節點。
通過快照觀察,入隊其實只是做了兩件事情:一是將入隊節點設置成當前隊尾節點的下一個節點。而是更新tail節點,如果tail節點的next節點為null,則將入隊節點設置成tail的next節點,如果tail節點的next節點不為空,則將入隊節點設置為tail節點。
入隊源碼:
public boolean offer(E e) { if (e == null) throw new NullPointerException();
//入隊前,創建入隊節點 Node<E> n = new Node<E>(e, null);
//死循環,入隊不成功則反復入隊 for (;;) { Node<E> t = tail;
//tail的next節點 Node<E> s = t.getNext(); if (t == tail) {
//tail的next節點為空 if (s == null) {
//表示t是尾節點,將t的next節點指向入隊節點 if (t.casNext(s, n)) {
更新tail節點,允許失敗 casTail(t, n); return true; } } else { casTail(t, s); } } } }
從源碼的角度來看:入隊過程主要就是定位出尾節點,然后使用CAS算法將入隊節點設置成尾節點的next節點,如不成功則重試。
設置tail節點所使用的CAS算法:
private boolean casTail(Node<E> cmp, Node<E> val) { return tailUpdater.compareAndSet(this, cmp, val); }
concurrent包的實現示意圖如下:

