鎖的劣勢java
Java在JDK1.5以前都是靠synchronized關鍵字保證同步的,這種經過使用一致的鎖定協議來協調對共享狀態的訪問,能夠確保不管哪一個線程 持有守護變量的鎖,都採用獨佔的方式來訪問這些變量,若是出現多個線程同時訪問鎖,那第一些線線程將被掛起,當線程恢復執行時,必須等待其它線程執行完他 們的時間片之後才能被調度執行,在掛起和恢復執行過程當中存在着很大的開銷。鎖還存在着其它一些缺點,當一個線程正在等待鎖時,它不能作任何事。若是一個線 程在持有鎖的狀況下被延遲執行,那麼全部須要這個鎖的線程都沒法執行下去。若是被阻塞的線程優先級高,而持有鎖的線程優先級低,將會致使優先級反轉 (Priority Inversion)。算法
volatile的優點apache
與鎖相比,volatile變量是一和更輕量級的同步機制,由於在使用這些變量時不會發生上下文切換和線程調度等操做,可是volatile變量也存在一些侷限:不能用於構建原子的複合操做,所以當一個變量依賴舊值時就不能使用volatile變量架構
悲觀鎖和樂觀鎖性能
獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的狀況,而且只有在確保其它線程不會形成干擾的狀況下執行,會致使其它所 有須要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另外一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於 衝突失敗就重試,直到成功爲止。ui
CAS操做this
Compare and Swap,比較並操做,CPU指令,在大多數處理器架構,包括IA3二、Space中採用的都是CAS指令,CAS的語義是「我認爲V的值應該爲A,若是 是,那麼將V的值更新爲B,不然不修改並告訴V的值實際爲多少」,CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線 程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。CAS有3個操做數,內存值V,舊的預期值 A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。atom
JVM對CAS的支持spa
在JDK1.5以前,若是不編寫明確的代碼就沒法執行CAS操做,在JDK1.5中引入了底層的支持,在int、long和對象的引用等類型上都公開了 CAS的操做,而且JVM把它們編譯爲底層硬件提供的最有效的方法,在運行CAS的平臺上,運行時把它們編譯爲相應的機器指令,若是處理器不支持CAS指 令,那麼JVM將使用自旋鎖。在原子類變量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了這些底層的 JVM支持爲數字類型的引用類型提供一種高效的CAS操做,而在java.util.concurrent中的大多數類在實現時都直接或間接的使用了這些 原子變量類。線程
下面代碼說明了CAS的語義(並非真正的實現,CAS的實現是調用native方法):
/** * Huisou.com Inc. * Copyright (c) 2011-2012 All Rights Reserved. */ package thread; import org.apache.http.annotation.GuardedBy; import org.apache.http.annotation.ThreadSafe; /** * @description * * @author chenzehe * @email hljuczh@163.com * @create 2013-1-6 上午09:02:47 */ @ThreadSafe public class SimulatedCAS { @GuardedBy("this") private int value; public synchronized int get() { return value; } public synchronized int comparedAndSwap(int expectedValue, int newValue) { int oldValue = value; if (oldValue == expectedValue) { value = newValue; } return oldValue; } public synchronized boolean compareAndSet(int expectedValue, int newValue) { return (expectedValue == comparedAndSwap(expectedValue, newValue)); } }
下面代碼演示了非阻塞計數器:
/** * Huisou.com Inc. * Copyright (c) 2011-2012 All Rights Reserved. */ package thread; /** * @description * * @author chenzehe * @email hljuczh@163.com * @create 2013-1-6 上午09:48:52 */ public class CasCounnter { private SimulatedCAS value; public int getValue() { return value.get(); } public int increment() { int v; do { v = value.get(); } while (v != value.comparedAndSwap(v, v + 1)); return v + 1; } }
AtomicInteger中實現自增的代碼爲:
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
表面上看起來,基於CAS的計數器彷佛比基於鎖的計數器在性能上更差一些,由於它還要執行更多的操做和更復雜的控制流,而且還依賴看似複雜的CAS 操做,實際上,當競爭程度不高時,基於CAS的計數器在性能上遠遠超過了基於鎖的計數器,而在沒有競爭時甚至更高,若是要快速獲取無競爭的鎖,那麼至少需 要一次CAS操做加上與其它鎖相關的操做,所以基於鎖的計數即便在最好的狀況下也會比基於CAS的計數器在通常狀況下執行更多的操做。因爲CAS在大多數 狀況下都能執行成功,所以硬件可以正確的預測while循環中的分支,從而把控制邏輯的開鎖降到最低。
鎖與原子變量的性能比較
在高度競爭的狀況下,鎖的性能將超過原子變量的性能,但在更真實的競爭狀況下,原子變量的性能將超過鎖的性能,這是由於鎖在發競爭時會掛起線程,從而下降 了CPU的使用率和共享內存總線上的同步通訊量,另外一方面,若是使用原子變量,那麼發出調用的類負責對競爭進行管理,在遇到競爭時當即重試,這一般是種正 確的作法,可是在競爭激烈環境下會致使更多的競爭。在實際的狀況中,任何一個程序都不會除了競爭鎖或原子變量而什麼事都不作,不會達到很高的競爭,因此在 更常見的狀況下,原子變量的效率會更高,在可伸縮性上要高於鎖。
非阻塞算法
若是在某個算法中,一個線程的失敗或掛起不會引發其它線程也失敗或掛起,那麼這個算法就被稱爲非阻塞算法;若是在算法的每一個步驟中都存在每一個線程可以執行 下去,那麼這種算法稱爲無鎖算法(Lock-Free)。若是在算法中僅將CAS用於協調線程之間的操做,而且能正確的實現,那麼它便是一種非阻塞算法, 也是一種無鎖算法。在非阻塞算法中一般不會出現死鎖和優先級反轉問題,但可能出現飢餓和活鎖問題,由於在算法中會反覆的重試。
下面代碼爲非阻塞的棧(使用Treiber算法):
package thread; import java.util.concurrent.atomic.AtomicReference; public class ConcurrentStack<T> { private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>(); public T push(T e) { Node<T> oldNode, newNode; for (;;) { // 這裏的處理很是的特別,也是必須如此的。 oldNode = stacks.get(); newNode = new Node<T>(e, oldNode); if (stacks.compareAndSet(oldNode, newNode)) { return e; } } } public T pop() { Node<T> oldNode, newNode; do{ oldNode = stacks.get(); if(oldNode==null){ return null; } newNode = oldNode.next; }while(!stacks.compareAndSet(oldNode, newNode)); return oldNode.object; } private static final class Node<T> { private T object; private Node<T> next; private Node(T object, Node<T> next) { this.object = object; this.next = next; } } }
若是在算法中的節點能夠被循環使用,那麼在使用CAS指令時就會出現這種問題,主要指在沒有垃圾回收機制的環境中,在CAS操做中,在更新以前先判斷V的 值是否仍然A,若是是的話就繼續執行更新操做,可是有的時候還須要知道「自從上次看到V的值爲A之後,這個值是否發生了變化?」,在某些算法中,若是V的 值先由A變成B,再由B變成A,那麼仍然認爲是發生了變化,並須要從新執行算法中的步驟。在這種狀況下,即便鏈表的頭節點仍然指向以前觀察到的節點,但也 不足以說明鏈表的內容沒有變化。若是經過垃圾回收機制仍然沒法避免ABA問題,那麼還有下面簡單方案:不是更新某個引用的值,而是更新兩個值,包括一個引 用和一個版本號,即便這個值由A變爲B,而後爲變爲A,版本號也是不一樣的。AtomicStampedReference和 AtomicMarkableReference支持在兩個變量上執行原子的條件更新。AtomicStampedReference更新一個「對象-引 用」二元組,經過在引用上加上「版本號」,從而避免ABA問題,AtomicMarkableReference將更新一個「對象引用-布爾值」的二元 組。
轉載自:http://chenzehe.iteye.com/blog/1762893