可見性:html
可見性是一種複雜的屬性,由於可見性中的錯誤老是會違背咱們的直覺。一般,咱們沒法確保執行讀操做的線程能適時地看到其餘線程寫入的值,有時甚至是根本不可能的事情。爲了確保多個線程之間對內存寫入操做的可見性,必須使用同步機制。java
可見性,是指線程之間的可見性,一個線程修改的狀態對另外一個線程是可見的。也就是一個線程修改的結果。另外一個線程立刻就能看到。好比:用volatile修飾的變量,就會具備可見性。volatile修飾的變量不容許線程內部緩存和重排序,即直接修改內存。因此對其餘線程是可見的。可是這裏須要注意一個問題,volatile只能讓被他修飾內容具備可見性,但不能保證它具備原子性。好比 volatile int a = 0;以後有一個操做 a++;這個變量a具備可見性,可是a++ 依然是一個非原子操做,也就是這個操做一樣存在線程安全問題。緩存
在 Java 中 volatile、synchronized 和 final 實現可見性。安全
原子性:多線程
原子是世界上的最小單位,具備不可分割性。好比 a=0;(a非long和double類型) 這個操做是不可分割的,那麼咱們說這個操做時原子操做。再好比:a++; 這個操做實際是a = a + 1;是可分割的,因此他不是一個原子操做。非原子操做都會存在線程安全問題,須要咱們使用同步技術(sychronized)來讓它變成一個原子操做。一個操做是原子操做,那麼咱們稱它具備原子性。java的concurrent包下提供了一些原子類,咱們能夠經過閱讀API來了解這些原子類的用法。好比:AtomicInteger、AtomicLong、AtomicReference等。ide
在 Java 中 synchronized 和在 lock、unlock 中操做保證原子性。性能
有序性:優化
Java 語言提供了 volatile 和 synchronized 兩個關鍵字來保證線程之間操做的有序性,volatile 是由於其自己包含「禁止指令重排序」的語義,synchronized 是由「一個變量在同一個時刻只容許一條線程對其進行 lock 操做」這條規則得到的,此規則決定了持有同一個對象鎖的兩個同步塊只能串行執行。spa
下面內容摘錄自《Java Concurrency in Practice》:線程
下面一段代碼在多線程環境下,將存在問題。
1 /** 2 * @author zhengbinMac 3 */ 4 public class NoVisibility { 5 private static boolean ready; 6 private static int number; 7 private static class ReaderThread extends Thread { 8 @Override 9 public void run() { 10 while(!ready) { 11 Thread.yield(); 12 } 13 System.out.println(number); 14 } 15 } 16 public static void main(String[] args) { 17 new ReaderThread().start(); 18 number = 42; 19 ready = true; 20 } 21 }
NoVisibility可能會持續循環下去,由於讀線程可能永遠都看不到ready的值。甚至NoVisibility可能會輸出0,由於讀線程可能看到了寫入ready的值,但卻沒有看到以後寫入number的值,這種現象被稱爲「重排序」。只要在某個線程中沒法檢測到重排序狀況(即便在其餘線程中能夠明顯地看到該線程中的重排序),那麼就沒法確保線程中的操做將按照程序中指定的順序來執行。當主線程首先寫入number,而後在沒有同步的狀況下寫入ready,那麼讀線程看到的順序可能與寫入的順序徹底相反。
在沒有同步的狀況下,編譯器、處理器以及運行時等均可能對操做的執行順序進行一些意想不到的調整。在缺少足夠同步的多線程程序中,要想對內存操做的執行春旭進行判斷,沒法獲得正確的結論。
這個看上去像是一個失敗的設計,但卻能使JVM充分地利用現代多核處理器的強大性能。例如,在缺乏同步的狀況下,Java內存模型容許編譯器對操做順序進行重排序,並將數值緩存在寄存器中。此外,它還容許CPU對操做順序進行重排序,並將數值緩存在處理器特定的緩存中。
Java語言提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操做通知到其餘線程。當把變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序。volatile變量不會被緩存在寄存器或者對其餘處理器不可見的地方,所以在讀取volatile類型的變量時總會返回最新寫入的值。
在訪問volatile變量時不會執行加鎖操做,所以也就不會使執行線程阻塞,所以volatile變量是一種比sychronized關鍵字更輕量級的同步機制。
當對非 volatile 變量進行讀寫的時候,每一個線程先從內存拷貝變量到CPU緩存中。若是計算機有多個CPU,每一個線程可能在不一樣的CPU上被處理,這意味着每一個線程能夠拷貝到不一樣的 CPU cache 中。
而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內存中讀,跳過 CPU cache 這一步。
1.保證此變量對全部的線程的可見性,這裏的「可見性」,如本文開頭所述,當一個線程修改了這個變量的值,volatile 保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。但普通變量作不到這點,普通變量的值在線程間傳遞均須要經過主內存(詳見:Java內存模型)來完成。
2.禁止指令重排序優化。有volatile修飾的變量,賦值後多執行了一個「load addl $0x0, (%esp)」操做,這個操做至關於一個內存屏障(指令重排序時不能把後面的指令重排序到內存屏障以前的位置),只有一個CPU訪問內存時,並不須要內存屏障;(什麼是指令重排序:是指CPU採用了容許將多條指令不按程序規定的順序分開發送給各相應電路單元處理)。
volatile 的讀性能消耗與普通變量幾乎相同,可是寫操做稍慢,由於它須要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行。