轉載自:http://www.cnblogs.com/Mainz/p/3556430.htmlhtml
synchronized關鍵字是基於阻塞的鎖機制,也就是說當一個線程擁有鎖的時候,訪問同一資源的其它線程須要等待,直到該線程釋放鎖,這裏會有些問 題:首先,若是被阻塞的線程優先級很高很重要怎麼辦?其次,若是得到鎖的線程一直不釋放鎖怎麼辦?(這種狀況是很是糟糕的)。還有一種狀況,若是有大量的 線程來競爭資源,那CPU將會花費大量的時間和資源來處理這些競爭(事實上CPU的主要工做並不是這些),同時,還有可能出現一些例如死鎖之類的狀況,最 後,其實鎖機制是一種比較粗糙,粒度比較大的機制,相對於像計數器這樣的需求有點兒過於笨重,所以,對於這種需求咱們期待一種更合適、更高效的線程安全機制。java
Java中long賦值不是原子操做,由於先寫32位,再寫後32位,分兩步操做,而AtomicLong賦值是原子操做,爲何?爲何 volatile能替代簡單的鎖,卻不能保證原子性?這裏面涉及volatile,是java中的一個我以爲這個詞在Java規範中從未被解釋清楚的神奇 關鍵詞,在Sun的JDK官方文檔是這樣形容volatile的:程序員
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.算法
意思就是說,若是一個變量加了volatile關鍵字,就會告訴編譯器和JVM的內存模型:這個變量是對全部線程共享的、可見的,每次jvm都會讀取最新寫入的值並使其最新值在全部CPU可見。volatile彷佛是有時候能夠代替簡單的鎖,彷佛加了volatile關鍵字就省掉了鎖。但又說volatile不能保證原子性(java程序員很熟悉這句話:volatile僅僅用來保證該變量對全部線程的可見性,但不保證原子性)。這不是互相矛盾嗎?緩存
不要將volatile用在getAndOperate場合(這種場合不原子,須要再加鎖),僅僅set或者get的場景是適合volatile的。安全
例如你讓一個volatile的integer自增(i++),其實要分紅3步:1)讀取volatile變量值到local; 2)增長變量的值;3)把local的值寫回,讓其它的線程可見。這3步的jvm指令爲:jvm
mov 0xc(%r10),%r8d ; Load inc %r8d ; Increment mov %r8d,0xc(%r10) ; Store lock addl $0x0,(%rsp) ; StoreLoad Barrier
注意最後一步是內存屏障。ide
內存屏障(memory barrier) 是一個CPU指令。基本上,它是這樣一條指令: a) 確保一些特定操做執行的順序; b) 影響一些數據的可見性(多是某些指令執行後的結果)。編譯器和CPU能夠在保證輸出結果同樣的狀況下對指令重排序,使性能獲得優化。插入一個內存屏障, 至關於告訴CPU和編譯器先於這個命令的必須先執行,後於這個命令的必須後執行。內存屏障另外一個做用是強制更新一次不一樣CPU的緩存。例如,一個寫屏障會 把這個屏障前寫入的數據刷新到緩存,這樣任何試圖讀取該數據的線程將獲得最新值,而不用考慮究竟是被哪一個cpu核心或者哪顆CPU執行的。oop
內存屏障(memory barrier) 和volatile什麼關係?上面的虛擬機指令裏面有提到,若是你的字段是volatile,Java內存模型將在寫操做後插入一個寫屏障指令,在讀操做 前插入一個讀屏障指令。這意味着若是你對一個volatile字段進行寫操做,你必須知道:一、一旦你完成寫入,任何訪問這個字段的線程將會獲得最新的 值。二、在你寫入前,會保證全部以前發生的事已經發生,而且任何更新過的數據值也是可見的,由於內存屏障會把以前的寫入值都刷新到緩存。post
明白了內存屏障(memory barrier)這個CPU指令,回到前面的JVM指令:從Load到store到內存屏障,一共4步,其中最後一步jvm讓這個最新的變量的值在全部線程可見,也就是最後一步讓全部的CPU內核都得到了最新的值,但中間的幾步(從Load到Store)是不安全的,中間若是其餘的CPU修改了值將會丟失。下面的測試代碼能夠實際測試voaltile的自增沒有原子性:
private static volatile long _longVal = 0; private static class LoopVolatile implements Runnable { public void run() { long val = 0; while (val < 10000000L) { _longVal++; val++; } } } private static class LoopVolatile2 implements Runnable { public void run() { long val = 0; while (val < 10000000L) { _longVal++; val++; } } } private void testVolatile(){ Thread t1 = new Thread(new LoopVolatile()); t1.start(); Thread t2 = new Thread(new LoopVolatile2()); t2.start(); while (t1.isAlive() || t2.isAlive()) { } System.out.println("final val is: " + _longVal); } Output:------------- final val is: 11223828 final val is: 17567127 final val is: 12912109
這是一段線程不安全的singleton(單例模式)實現,儘管使用了volatile:
public class wrongsingleton { private static volatile wrongsingleton _instance = null; private wrongsingleton() {} public static wrongsingleton getInstance() { if (_instance == null) { _instance = new wrongsingleton(); } return _instance; } }
就拿AtomicLong來講,它既解決了上述的volatile的原子性沒有保證的問題,又具備可見性。它是如何作到的?固然就是上文《非阻塞同步算法與CAS(Compare and Swap)無鎖算法》提到的CAS(比較並交換)指令。 其實AtomicLong的源碼裏也用到了volatile,但只是用來讀取或寫入,見源碼:
public class AtomicLong extends Number implements java.io.Serializable { private volatile long value; /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public AtomicLong(long initialValue) { value = initialValue; } /** * Creates a new AtomicLong with initial value {@code 0}. */ public AtomicLong() { }
其CAS源碼核心代碼爲:
int compare_and_swap (int* reg, int oldval, int newval) { ATOMIC(); int old_reg_val = *reg; if (old_reg_val == oldval) *reg = newval; END_ATOMIC(); return old_reg_val; }
虛擬機指令爲:
mov 0xc(%r11),%eax ; Load mov %eax,%r8d inc %r8d ; Increment lock cmpxchg %r8d,0xc(%r11) ; Compare and exchange
由於CAS是基於樂觀鎖的,也就是說當寫入的時候,若是寄存器舊值已經不等於現值,說明有其餘CPU在修改,那就繼續嘗試。因此這就保證了操做的原子性。