Java的挥发性,同步,原子例如

Java的挥发性,同步,原子例如

问题描述:

您好我正在读Java并发在实践中,我读有趣的说法指出,Java的挥发性,同步,原子例如

锁定既能保证可见性和原子;易变的 变量只能保证能见度。

任何一个可以请解释,如果变量声明为挥发性所有其他线程读取更新获得价值那就是为什么我关心像声明中的原子:counter = counter + 1;

在此先感谢。

+0

退房这个职位。 https://stackoverflow.com/questions/3519664/difference-between-volatile-and-synchronized-in-java –

+1

“为什么我关心语句中的原子一样'计数器=计数+ 1;'”。那么如果你想让你的柜台有正确的价值,你应该关心。 – Kayaman

+0

@so_what:请标出正确的答案,如果它可以帮助你 –

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操作+同步=完美 –

以下是一个演示这一动荡对自己是不是一个自包含例如可执行应用程序足够。四个线程每次增加一个计数器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变量来获取最新值,然后使用同步来正确更新值。 –