为什么Volatile表现得很奇怪

Kum*_*tra 8 java multithreading volatile synchronized

我最近经历过volatile关键字这种奇怪的行为.我所知道的,

  1. volatile关键字应用于变量,以将一个线程对变量数据所做的更改反映到另一个线程上.

  2. volatile关键字阻止在线程上缓存数据.

我做了一个小测试........

  1. 我使用了一个名为count的整数变量,并在其上使用了volatile关键字.

  2. 然后制作2个不同的线程将变量值增加到10000,因此最终结果应为20000.

  3. 但事实并非如此,使用volatile关键字我一直没有得到20000,而是18534,15000等......有时候是20000.

  4. 但是虽然我使用了synchronized关键字,但它工作得很好,为什么......?

任何人都可以解释我挥发性关键字的这种行为.

我发布带有volatile关键字的代码,以及带有synchronzied关键字的代码.

以下代码与变量计数上的volatile关键字行为不一致

public class SynVsVol implements Runnable{

    volatile int  count = 0;

    public void go(){

        for (int i=0 ; i<10000 ; i++){
             count = count + 1;
         }
    }

    @Override
    public void run() {
        go();
    }

    public static void main(String[] args){

        SynVsVol s = new SynVsVol();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Total Count Value: "+s.count);
    }
}
Run Code Online (Sandbox Code Playgroud)

以下代码与方法go()上的synchronized关键字完美匹配.

public class SynVsVol implements Runnable{

    int  count = 0;

    public synchronized void go(){
        for (int i=0 ; i<10000 ; i++){
             count = count + 1;
         }
    }

    @Override
    public void run() {
        go();
    }

    public static void main(String[] args){

        SynVsVol s = new SynVsVol();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Total Count Value: "+s.count);
    }
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*dam 12

count = count + 1不是原子的.它有三个步骤:

  1. 读取变量的当前值
  2. 增加价值
  3. 将新值写回变量

这三个步骤交织在一起,导致不同的执行路径,导致不正确的值.使用AtomicInteger.incrementAndGet(),如果你想避免synchronized关键字来代替.

因此,尽管volatile关键字的行为与您所描述的几乎相同,但它仅适用于每个单独的操作,而不适用于所有三个操作.


das*_*ght 7

volatile关键字是不是原始的同步.它只是防止在线程上缓存值,但它不会阻止两个线程修改相同的值并同时将其写回.

假设有两个线程需要递增计数器,现在设置为5.两个线程都看到5,从中取出6,并将其写回计数器.如果计数器不是volatile,则两个线程都可以假设它们知道值为6,并跳过下一个读取.然而,它是不稳定的,所以它们都会读回6,并继续递增.由于线程没有进入锁定步骤,​​您可能会在输出中看到与10000不同的值,但实际上您几乎没有机会看到20000.