8

我最近经历了 volatile 关键字的这种奇怪行为。据我所知,

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

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

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

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

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

  3. 但情况并非总是如此,使用 volatile 关键字,我不会始终获得 20000,而是 18534、15000 等......有时是 20000。

  4. 但是当我使用同步关键字时,它工作得很好,为什么......??

谁能解释一下 volatile 关键字的这种行为。

我使用 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);
    }
}

以下代码与 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);
    }
}
4

4 回答 4

12

count = count + 1不是原子的。它分为三个步骤:

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

这三个步骤交织在一起,导致执行路径不同,导致值不正确。AtomicInteger.incrementAndGet()如果要避免使用 synchronized 关键字,请改用。

因此,尽管volatile关键字的作用与您描述的差不多,但这仅适用于每个单独的操作,而不适用于所有三个操作。

于 2012-07-01T10:54:25.187 回答
7

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

假设两个线程需要增加计数器,现在设置为 5。两个线程都看到 5,从中取出 6,然后将其写回计数器。如果计数器不是volatile,则两个线程都可以假设它们知道值为 6,并跳过下一次读取。然而,它是易变的,所以他们都会读回 6,并继续递增。由于线程没有步调一致,您可能会在输出中看到与 10000 不同的值,但您几乎不可能看到 20000。

于 2012-07-01T10:54:31.570 回答
4

变量 is 的事实volatile并不意味着它所涉及的每个操作都是原子的。例如,这一行在SynVsVol.Go

count = count + 1;

将首先count读取,然后递增,然后将结果写回。如果某个其他线程将同时执行它,则结果取决于命令的交错。

现在,当您添加 , 时syncronized,会SynVsVol.Go以原子方式执行。即增量是由一个线程整体完成的,另一个线程在count完成之前不能修改。

最后,缓存仅在同步块中修改的成员变量要容易得多。编译器可以在获取监视器时读取它们的值,将其缓存在寄存器中,在该寄存器上完成所有更改,并最终在释放监视器时将其刷新回主内存。当您在同步块中调用时也是这种情况wait,并且当您使用其他线程notify时:缓存的成员变量将被同步,并且您的程序将保持连贯性。即使成员变量未声明为 volatile,也可以保证:

同步确保线程在同步块之前或期间写入的内存以可预测的方式对在同一监视器上同步的其他线程可见。

于 2012-07-01T10:53:42.117 回答
3

您的代码已损坏,因为它将 a 上的读取和增量操作volatile视为原子操作,但事实并非如此。该代码不包含数据竞争,但它确实包含int.

于 2012-07-01T10:53:55.580 回答