2017-12-18 96 views
0

您好我正在讀Java併發在實踐中,我讀有趣的說法指出,Java的揮發性,同步,原子例如

鎖定既能保證可見性和原子;易變的 變量只能保證能見度。

任何一個可以請解釋,如果變量聲明爲揮發性所有其他線程讀取更新獲得價值那就是爲什麼我關心像聲明中的原子:counter = counter + 1;

在此先感謝。

+0

退房這個職位。 https://stackoverflow.com/questions/3519664/difference-between-volatile-and-synchronized-in-java –

+1

「爲什麼我關心語句中的原子一樣'計數器=計數+ 1;'」。那麼如果你想讓你的櫃檯有正確的價值,你應該關心。 – Kayaman

+0

@so_what:請標出正確的答案,如果它可以幫助你 –

回答

2

volatile關鍵字的效果大約是每個單獨的讀或寫操作在該變量是原子的。但是,值得注意的是,需要多個讀/寫操作的操作 - 例如i ++,相當於i = i + 1,它執行一次讀取和一次寫入 - 不是原子的,因爲另一個線程可能會在讀和寫之間寫入i。

的原子類,像的AtomicInteger和的AtomicReference,提供更廣泛的各種操作的原子,具體包括增量的AtomicInteger。

這就是爲什麼你需要關心的原子像反陳述=計數器+ 1

請查看這篇文章Volatile Vs Atomic

+0

這個答案似乎說明如何解決由這些非原子操作的問題。但它並沒有回答爲什麼這是一個問題開始的問題。即在兩個線程中增加一個變量的情況下,如果兩個線程在遞增之前讀取相同的值,則會導致只有一個增量 – Cruncher

+0

「但值得注意的是,需要多個讀/寫操作 - 例如i ++,這相當於i = i + 1,它執行一次讀取和一次寫入 - 不是原子的,因爲另一個線程可能會在讀取和寫入之間寫入i「它不回答爲什麼需要原子性的問題在乎?請在結束之前清楚地閱讀它@Cruncher –

+0

「另一個線程可能會在讀和寫之間寫入」語句對我來說很有意義,因爲我已經完成了對此的演示,但是您可以考慮以下線程安全用例: 1)易變+原子變量(非個人)=完美 2)易失性+同步塊=完美 3)個人atmoic操作+同步=完美 –

2

以下是一個演示這一動盪對自己是不是一個自包含例如可執行應用程序足夠。四個線程每次增加一個計數器10,000次,所以你最終預計該計數器爲40,000。它使用一個原始的int變量和一個AtomicInt,並且每次嘗試5次這個練習。

import java.util.Collections; 
import java.util.concurrent.*; 
import java.util.concurrent.atomic.AtomicInteger; 

class AtomicDemo { 
    interface Demo extends Callable<Void> { 
     int getCounter(); 
    } 

    static class UsePrimitive implements Demo { 
     private volatile int counter = 0; 

     public Void call() throws Exception { 
      for (int i = 1; i <= 10000; ++i) { 
       ++counter; 
      } 
      return null; 
     } 

     public int getCounter() { 
      return counter; 
     } 
    } 

    static class UseAtomic implements Demo { 
     final AtomicInteger counter = new AtomicInteger(0); 

     public Void call() throws Exception { 
      for (int i = 1; i <= 10000; ++i) { 
       counter.incrementAndGet(); 
       System.out.print(""); 
      } 
      return null; 
     } 

     public int getCounter() { 
      return counter.get(); 
     } 
    } 

    public static void main(String[] args) throws Exception { 
     ExecutorService exec = Executors.newFixedThreadPool(4); 
     for (int i = 1; i <= 5; ++i) { 
      Demo demo = new UsePrimitive(); 
      exec.invokeAll(Collections.nCopies(4, demo)); 
      System.out.println("Count to 40000 using primitive, attempt number " + i + ": " + demo.getCounter()); 
     } 
     for (int i = 1; i <= 5; ++i) { 
      Demo demo = new UseAtomic(); 
      exec.invokeAll(Collections.nCopies(4, demo)); 
      System.out.println("Count to 40000 using atomic, attempt number " + i + ": " + demo.getCounter()); 
     } 
     exec.shutdownNow(); 
    } 
} 

典型輸出:

Count to 40000 using primitive, attempt number 1: 39711 
Count to 40000 using primitive, attempt number 2: 39686 
Count to 40000 using primitive, attempt number 3: 39972 
Count to 40000 using primitive, attempt number 4: 39840 
Count to 40000 using primitive, attempt number 5: 39865 
Count to 40000 using atomic, attempt number 1: 40000 
Count to 40000 using atomic, attempt number 2: 40000 
Count to 40000 using atomic, attempt number 3: 40000 
Count to 40000 using atomic, attempt number 4: 40000 
Count to 40000 using atomic, attempt number 5: 40000 

你看,只有AtomicInt你總是得到預期的結果。

+0

偉大我也試過,並得出結論,揮發性只是爲了獲得線程的最新值不防止違反原子性,如果上下文切換髮生在一些不幸的時間然後我們不會得到正確的值。當使用Atomicxxx類時,他們使用volatile變量來獲取最新值,然後使用同步來正確更新值。 –