java並發編程(8)原子變數和非阻塞的同步機制,java變數
原子變數和非阻塞的同步機制 一、鎖的劣勢
1.在多線程下:鎖的掛起和恢複等過程存在著很大的開銷(及時現代的jvm會判斷何時使用掛起,何時自旋等待)
2.volatile:輕量層級的同步機制,但是不能用於構建原子複合操作
因此:需要有一種方式,在管理線程之間的競爭時有一種粒度更細的方式,類似與volatile的機制,同時還要支援原子更新操作
二、CAS
獨佔鎖是一種悲觀的技術--它假設最壞的情況,所以每個線程是獨佔的
而CAS比較並交換:compareAndSwap/Set(A,B):我們認為記憶體處值是A,如果是A,將其修改為B,否則不進行操作;返回記憶體處的原始值或是否修改成功
如:類比CAS操作
//類比的CASpublic class SimulatedCAS { private int value; public synchronized int get() { return value; } //CAS操作 public synchronized int compareAndSwap(int expectedValue, int newValue) { int oldValue = value; if (oldValue == expectedValue) { value = newValue; } return oldValue; } public synchronized boolean compareAndSet(int expectedValue, int newValue) { return (expectedValue == compareAndSwap(expectedValue, newValue)); }}//典型使用情境public class CasCounter { private SimulatedCAS value; public int getValue() { return value.get(); } public int increment() { int v; do { v = value.get(); } while { (v != value.compareAndSwap(v, v + 1)); } return v + 1; }}
JAVA提供了CAS的操作
原子狀態類:AtomicXXX的CAS方法
JAVA7/8:對Map的操作:putIfAbsent、computerIfAbsent、computerIfPresent.........
三、原子變數類
AtomicRefence原子更新對象,可以是自訂的對象;如:
public class CasNumberRange { private static class IntPair { // INVARIANT: lower <= upper final int lower; //將值定義為不可變域 final int upper; //將值定義為不可變域 public IntPair(int lower, int upper) { this.lower = lower; this.upper = upper; } } private final AtomicReference<IntPair> values = new AtomicReference<IntPair>(new IntPair(0, 0)); //封裝對象 public int getLower() { return values.get().lower; } public int getUpper() { return values.get().upper; } public void setLower(int i) { while (true) { IntPair oldv = values.get(); if (i > oldv.upper) { throw new IllegalArgumentException("Can't set lower to " + i + " > upper"); } IntPair newv = new IntPair(i, oldv.upper); //屬性為不可變域,則每次更新建立對象 if (values.compareAndSet(oldv, newv)) { //原子更新,如果在過程中有線程修改了,則其他線程不會更新成功,因為oldv與記憶體處值就不同了 return; } } } //同上 public void setUpper(int i) { while (true) { IntPair oldv = values.get(); if (i < oldv.lower) throw new IllegalArgumentException("Can't set upper to " + i + " < lower"); IntPair newv = new IntPair(oldv.lower, i); if (values.compareAndSet(oldv, newv)) return; } }}
效能問題:使用原子變數在中低並發(競爭)下,比使用鎖速度要快,一般情況下是比鎖速度快的
四、非阻塞演算法
許多常見的資料結構中都可以使用非阻塞演算法
非阻塞演算法:在多線程中,工作是否成功有不確定性,需要迴圈執行,並通過CAS進行原子操作
1、上面的CasNumberRange
2、棧的非阻塞演算法:只儲存頭部指標,只有一個狀態
//棧實現的非阻塞演算法:單向鏈表public class ConcurrentStack <E> { AtomicReference<Node<E>> top = new AtomicReference<Node<E>>(); public void push(E item) { Node<E> newHead = new Node<E>(item); Node<E> oldHead; do { oldHead = top.get(); newHead.next = oldHead; } while (!top.compareAndSet(oldHead, newHead));//CAS操作:原子更新操作,迴圈判斷,非阻塞 } public E pop() { Node<E> oldHead; Node<E> newHead; do { oldHead = top.get(); if (oldHead == null) { return null; } newHead = oldHead.next; } while (!top.compareAndSet(oldHead, newHead));//CAS操作:原子更新操作,迴圈判斷,非阻塞 return oldHead.item; } private static class Node <E> { public final E item; public Node<E> next; public Node(E item) { this.item = item; } }}
3、鏈表的非阻塞演算法:頭部和尾部的快速存取,儲存兩個狀態,更加複雜
public class LinkedQueue <E> { private static class Node <E> { final E item; final AtomicReference<LinkedQueue.Node<E>> next; public Node(E item, LinkedQueue.Node<E> next) { this.item = item; this.next = new AtomicReference<LinkedQueue.Node<E>>(next); } } private final LinkedQueue.Node<E> dummy = new LinkedQueue.Node<E>(null, null); private final AtomicReference<LinkedQueue.Node<E>> head = new AtomicReference<LinkedQueue.Node<E>>(dummy); private final AtomicReference<LinkedQueue.Node<E>> tail = new AtomicReference<LinkedQueue.Node<E>>(dummy); //儲存尾節點 public boolean put(E item) { LinkedQueue.Node<E> newNode = new LinkedQueue.Node<E>(item, null); while (true) { LinkedQueue.Node<E> curTail = tail.get(); LinkedQueue.Node<E> tailNext = curTail.next.get(); if (curTail == tail.get()) { if (tailNext != null) { // 處於中間狀態,更新尾節點為當前尾節點的next tail.compareAndSet(curTail, tailNext); } else { // 將當前尾節點的next 設定為新節點:鏈表 if (curTail.next.compareAndSet(null, newNode)) { /** * 此處即為中間狀態,雖然在這裡進行了兩次原子操作,整體不是原子的,但是通過演算法保證了安全: * 原因是處於中間狀態時,如果有其他線程進來操作,則上面那個if將執行; * 上面if的操作是來協助當前線程完成更新尾節點操作,而當前線程的更新就會失敗返回,最終則是更新成功 */ // 連結成功,尾節點已經改變,則將當前尾節點,設定為新節點 tail.compareAndSet(curTail, newNode); return true; } } } } }}
3.原子域更新器
上面的邏輯,實現了鏈表的非阻塞演算法,使用Node來儲存頭結點和尾節點
在實際的ConcurrentLinkedQueue中使用的是基於反射的AtomicReferenceFiledUpdater來封裝Node
五、ABA問題
CAS操作中容易出現的問題:
判斷值是否為A,是的話就繼續更新操作換為B;
但是如果一個線程將值A改為C,然後又改回A,此時,原線程將判斷A=A成功執行更新操作;
如果把A改為C,然後又改回A的操作,也需要視為變化,則需要對演算法進行最佳化
解決:添加版本號碼,每次更新操作都要更新版本號碼,即使值是一樣的