關於volatile和同步相關的東西,網上有太多錯誤和解釋不清的東西, 因此查閱相關書籍和文章後總結以下, 若是仍是也存在不正確的內容,請必定要指出來, 以避免誤人子弟:)html
1. 原子性與可視性java
原子性是指操做不能被線程調度機制中斷, 除long和double以外的全部基本類型的讀或寫操做都是原子操做,注意這裏說的讀寫, 僅指如return i, i = 10, 對於像i++這種操做,包含了讀,加1,寫指令,因此不是原子操做。 對於long和double的讀寫,在64位JVM上會把它們看成兩個32位來操做,因此不具有原子性。數組
在定義long和double類型變量時,若是使用volatile來修飾,那麼也能夠得到原子性,除此之外,volatile與原子性沒有直接關係。緩存
可視性,volatile的主要做用就是確保可視性,那麼什麼是可視性?
在系統中(多處理器更加明顯),對某一變量的修改有時會暫時保存在本地處理器的緩存中,尚未寫入共享內存,這時候有另一個線程讀取變量在共享內存的值,那麼這個修改對這個線程就是不可視的。
Volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。並且,當成員變量發生變化時,強迫線程將變化值直接寫到共享內存。這樣在任什麼時候刻,兩個不一樣的線程老是看到某個成員變量的同一個值。 原子操做不必定就有可視性, 好比賦值,i = 10, 若是i沒有被特別修飾, 那麼由於緩存的緣由, 它仍然多是不可視的安全
因此原子性和可視性是徹底不一樣的兩個概念多線程
2. volatile的應用場景
詳細能夠參考java語言架構師Brain Geotz的文章
Java 中volatile 變量能夠被看做是一種 「程度較輕的 synchronized」;與 synchronized 塊相比,volatile 優勢是所需的編碼較少,而且運行時開銷也較少, 不會引發線程阻塞。Volatile 變量具備 synchronized 的可視性特性,可是不具有原子特性。
這就致使Volatile 變量可用於提供線程安全,可是應用場景很是有限,在一些經典java書裏,基本都不推薦使用volatile替代synchronized來實現同步,由於風險較大, 很容易出錯。
Brain給出的使用volatile實現線程安全的條件:
對變量的寫操做不依賴於當前值。 (count++這種就不行了)
該變量沒有包含在具備其餘變量的不變式中(Invariants,例如 「start <=end」)。架構
個人理解是, 這兩個條件都是由於volatile不能提供原子性致使的, 若是多線程執行的一個操做不是原子性的, 使用volatile時就必定要慎重。
若是知足這兩個條件, 多線程執行的操做是原子性的, 那就是可使用,如:
將 volatile 變量做爲狀態標誌使用併發
volatile boolean shutdownRequested; . ... public void shutdown() { shutdownRequested = true; } //不依賴當前值,原子操做 public void doWork() { while (!shutdownRequested) { // do stuff } }
文章中的其餘幾種模式, 也都差很少這個意思。ide
還有一種狀況,若是讀操做遠遠超過寫操做,能夠結合使用內部鎖和 volatile 變量來減小公共代碼路徑的開銷。
結合使用 volatile 和 synchronized 實現 「開銷較低的讀-寫鎖」測試
@ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } //使用volatile替代synchronized public synchronized int increment() { return value++; }
然而,你能夠在讀操做中使用 volatile 確保當前值的可見性,所以可使用鎖進行全部變化的操做,使用 volatile 進行只讀操做。其中,鎖一次只容許一個線程訪問值,volatile 容許多個線程執行讀操做,所以當使用 volatile 保證讀代碼路徑時,要比使用鎖執行所有代碼路徑得到更高的共享度 —— 就像讀-寫操做同樣。
3. synchronized
class AtomTest implements Runnable { private volatile int i = 0; public int getVal() {return i;} public synchronized void inc() {i++; i++;} @Override public void run() { while (true) { inc(); } } } public class TestThread { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); AtomTest at = new AtomTest(); exec.execute(at); while (true) { int val = at.getVal(); if (val % 2 != 0) { System.out.println(val); System.exit(0); } } } }
結果會輸出奇數, 退出程序, 緣由是getVal讀到了inc的中間值。 這種狀況只能在getVal方法前加synchronized
在讀取的時候也加鎖, 這樣在讀的時候若是正在寫, 那麼等待, 因此就不會讀到inc的中間值。
關於synchronized值得注意的幾個點:
1) 全部對象都含有一個鎖,當調用到synchronized(object)塊時,先檢測obj有沒有加鎖,若是有, 阻塞, 若是沒有, 對object加鎖, 執行完後釋放鎖。
2) synchronized void f() {//...} 等價於 void f() { synchronized(this) {//...} }, 在當前對象上加鎖
3) synchronized 提供原子性和可視性, 被它徹底保護的變量不須要用volatile
4) synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類須要你顯式的指定它的某個方法爲synchronized方法。
注意第一點, 很是重要:雖然sync塊能夠包裹一段代碼,可是鎖是加對象上,不是加在代碼上,它的工做機制以下:
對於synchronized(obj) {//...}: 先檢測obj有沒有加鎖,若是有, 阻塞, 若是沒有, 對obj加鎖, 執行塊中的代碼,完畢後釋放鎖。這裏只檢測obj對象上的鎖,不關注代碼塊裏的代碼或者對象。
因此, 加鎖的範圍由obj決定,理解了這一點, 下面的不少種狀況就會很容易理解:
1. 當兩個併發線程訪問同一個對象object中的這個相同synchronized(this)同步代碼塊時,一個時間內針對該對象的操做只能有一個線程獲得執行。另外一個線程必須等待。
- 若是同一對象已經加鎖, 另外一線程執行到sync塊,檢測到有鎖掛起。
2. 然而,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。
- 非sync代碼,不關注對象是否加鎖
3. 當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對該object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。
- synchronized(this) 只是關注this對象, 只要this已加鎖,執行到同一對象中不一樣方法的sync塊時,也會阻塞。
4. 不一樣的對象實例的synchronized(this)方法是不相干擾的。也就是說,其它線程照樣能夠同時訪問相同類的另外一個對象實例中的synchronized方法。
- 能夠同時訪問不一樣對象中的sync塊, 緣由很簡單, 由於synchronized(this),關注的是this對象,不一樣對象的this是不同的。
5. 同理,也能夠對其餘對象加鎖,
1) 對於類中的成員對象: private Integer i = new Integer(0); synchronized(i) {//...} i建立在堆上,每一個對象有一個i,因此效果與synchronized(this)同樣。
2) 對於靜態成員對象: private static Integer i = new Integer(0); synchronized(i) {//...}: i建立在靜態區,屬於類, 因此效果與synchronized(ClassName.class)同樣。
6.對類對象加鎖時,對該類的全部對象都起做用:synchronized(ClassName.class) {//...}
最後我在測試代碼時,發現對於private Integer i = 0; synchronized(i) {//...}
鎖的效果是全局的,推測多是Integer對0進程打包時,自動生成的這個對象可能在常量區。 後來查了下資料才發現並不是如此:
Integer實現中有一個IntegerCache類,它包含一個靜態的Integer數組,在類加載時就將-128 到 127 的Integer對象建立了,並保存在cache數組中,一旦程序調用valueOf 方法,若是i的值是在-128 到 127 之間就直接在cache緩存數組中去取Integer對象。 因此這裏的i引用的是整個全局數組裏值, 因此鎖也是全局的了。。。
若是改爲private Integer i = 300, 而後加鎖就只在本對象有效了。 緣由是i不在緩存範圍,因此建立在了堆上。
refer http://blog.csdn.net/xiaohai0504/article/details/6885137