为什么修改一个线程中的共享变量会影响另一个线程,即使不使用volatile?

时间:2012-11-14 10:08:11

标签: java multithreading unit-testing

这里我有三个简单的类:

第1课:

public class ThreadSyncMain {

public static int count = 0; // volatile is not use

public static void main(String[] args) {

    Thread thread1 = new Thread( new Thread1(),"Thread1" ); 
    Thread thread2 = new Thread( new Thread2(),"Thread2");

    thread1.start();
    thread2.start();

}

}

第2课:

public class Thread1 implements Runnable{
public void run() {

    System.out.println("Thread1 Count :: "+ThreadSyncMain.count);
    ThreadSyncMain.count++;
}

}

第3课:

public class Thread2 implements Runnable{
public void run() {

        System.out.println("Thread2 Count :: "+ThreadSyncMain.count);

}
}

输出结果为:

Thread1 Count :: 0
Thread2 Count :: 1

这意味着thread1更改了count的值。那么为什么更改thread1会影响thread2,因为我没有使用任何“volatile”关键字。在这种情况下,“volatile”关键字不是问题吗?如何修改代码以测试“volatile”?

提前致谢。

更新部分: 在进行一些命中和试验测试之后,我正在更新代码。 1级保持不变。这是更新的代码:

第2类:我加了100毫秒延迟。

public class Thread1 implements Runnable{
public void run() {

    System.out.println("Thread1 Count :: "+ThreadSyncMain.count);

    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    ThreadSyncMain.count++;
}

}

第3类:添加while循环。在里面,计数会持续受到监控。

public class Thread2 implements Runnable{
public void run() {

    while(true)
    {
        if(ThreadSyncMain.count == 1)
        { 
            System.out.println("Thread2 Count :: "+ThreadSyncMain.count);
        }
    }

}
}

现在在这种情况下我得到以下输出:
1.如果class1输出中未使用“volatile”,则为:

 Thread1 Count :: 0

2。如果在class1输出中使用“volatile”,则为:

Thread1 Count :: 0
Thread2 Count :: 1
Thread2 Count :: 1
Thread2 Count :: 1
.
.
.

为什么在这种情况下会出现波动?

3 个答案:

答案 0 :(得分:3)

每个线程都有一个内存视图。如果不使用锁,则不保证这些视图在线程之间保持一致。因此,如上所述共享变量(没有volatile)将起作用,因为它在线程中可见,但可能会给您不可靠的结果。使用volatile关键字意味着变量在线程之间始终读取。

请参阅here,特别注意:

  

易失性字段是用于通信的特殊字段   线程之间的状态。每次读取volatile都会看到最后一次写入   通过任何线程对那个易变的;实际上,他们是由   程序员作为字段,看到“陈旧”是永远不可接受的   缓存或重新排序的结果。

答案 1 :(得分:3)

Volatile保证线程1的副作用对线程2可见。如果没有volatile,则可能会或可能不会显示更改。

测试volatile的效果很困难,因为它依赖于低级方面,如硬件架构,线程实现,编译器优化以及甚至调度程序的确切时序。

如果我这样做,我会编写产生高并发性的多线程测试,并确保我在多处理器实现上运行。然后我可能会观察有和没有volatile的代码之间的差异。测试的结果仍然是不确定的。

答案 2 :(得分:1)

编译器可能没有缓存计数器,因为它是一个类范围的变量。所以写入是在内存中。

如果要测试易失性和非易失性写/读..

public class VolatileExperiment
{
  private int counter ;
  private volatile int volatile_counter;
  public void Counter()
  {
     new Thread( new Runnable(){
     public void run()
     {
        ++counter;
        ++volatile_counter;
        //print
      }
    }).start();


   new Thread( new Runnable(){
     public void run()
     {
       ++counter;
       ++volatile_counter;
       //print
      }
    }).start();
    //print counter 
   //print volatile 
  }

}

使用volatile确保编译器不优化代码,因此写入在内存中完成,而不是在线程内存中完成。因此,您应该看到volatile_counter已更新..而counter可能不会受到影响