关于volatile的问题

public class TestThread {
  private static int 普通变量 = 0;
  private volatile static int volatile变量 = 0;

  public static void main(String[] args) {
    new Thread(){
      public void run(){
        System.out.println("开始循环");
        while(普通变量==0&&volatile变量 ==0){
          //System.out.println("12312321312312321");
        }
        System.out.println("结束循环");
        System.exit(0);
      }
    }.start();

    new Thread(){
      public void run(){
          try {
            Thread.sleep(1500);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        System.out.println("开始赋值");
          普通变量 = 1;
      }
    }.start();
  }
}

程序输出:

开始循环
开始赋值
结束循环

正常结束了

但是如果把volatile变量的volatile修饰符去掉,那么程序将会死循环。

这个问题我目前能给出的猜想是,jmm获取volatile变量的同时也会刷新获取非volatile变量的值?

希望并发大神能帮忙回答一下这个问题

阅读 2.2k
3 个回答

这个与硬件的缓存失效机制有关。简单来说缓存失效是按行进行失效,而不是按变量。因为两个变量的定义离得很近,因此在内存/缓存中很可能也是在一起的。这就导致volatile变量失效了缓存的时候连同普通变量一起失效了。

你好,以上的问题在并发下出现了共享变量不可见的问题,synchronized和volatile都是为了保护多线程的安全,synchronized具有原子性和可见性,而volatile只有可见性,上面出现接受循环是因为出现了线程交叉,volatile不能保证原子性,多线程间共享变量的可见性是其安全的保障。如下图
图片说明

1、把工作内存A中更新过的共享变量刷新到主内存中
2、将主内存中最新的共享变量的值更新到工作内存B中
而在期间不允许其他线程去更新最新的共享变量,但我们的代码去发生了,第一个线程的判断执行过程中,第二线程对共享变量(普通变量)进行了赋值修改,当我使用synchronized重新运行以上方法时(synchronized互斥加锁解锁),如下代码

private static int num = 0;
    private static int volatileNum = 0;

    public static void main(String[] args) {
        new Thread(){
            public synchronized void run(){
                System.out.println("开始循环");
                while(num==0&&volatileNum ==0){
                    //System.err.println("--");
                }
                System.out.println("结束循环");
                System.exit(0);
            }
        }.start();

        new Thread(){
            public synchronized void run(){
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("开始赋值");
                num = 1;
            }
        }.start();
    }

其运行结果就依然是无限循环,因为synchronized保证了单线程中的num与volatileNum其获取时是主内存的最新值,并在运行没有结束时不会推送更新到主存储中,线程二更新后对于第一次获取到值的判断线程而言还没有获取到num的最新值,不过我发现一个有趣的问题,在while中写入System.out.println(num);后,即使是synchronized下,程序也结束了循环,但是如果while中没有任何执行代码则会一直循环下去。
这让我有一个奇怪的想法:我修改了一下代码

while (true){
        if(num==0&&volatileNum ==0){
            System.err.println("继续循环");
        }else if(num==1&&volatileNum ==0){
            System.out.println("结束循环");
            System.exit(0);
        }
    }

其结果是在赋值时,结束了循环,即赋值线程更新num变量后,推送到主内存中,而判断线程也获取到了最新的num变量。且这时取出synchronized对其也没有过多影响。将volatileNum重新加volatile后的结果也是一样的。

多线程真的很有趣,我发现我还要去在了解一下,如果你有新的看法也可以告诉我。我不认为我回答的就是正确的。
你可以看看我最近写的两个文章,我想听听你的意见。
【Java猫说】Java多线程之内存可见性(上篇)
【Java猫说】Java多线程之内存可见性(下篇)

给代码增加一些输出信息如下:

public class Main  {
    private static int a = 0;

    private volatile static int b = 0;

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(){
            public void run(){
                System.out.println("开始循环");
                System.out.println("a:"+a);
                while(a==0&&b==0){
                    System.out.println("a:"+a);
                    //System.out.println("12312321312312321");
                }
                System.out.println("a:"+a);
                System.out.println("结束循环");
                System.exit(0);
            }
        }.start();

        new Thread(){
            public void run(){
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("开始赋值");
                a=1;
            }
        }.start();
    }
}

运行代码输出

开始循环
a:0
a:0
a:0
 .
 .
 .
a:0
a:0
开始赋值
a:1
结束循环

之后交换a,b位置如下:while(b==0&&a==0){
惊讶的发现输出有一些不同:

开始循环
a:0
a:0
a:0
 .
 .
 .
a:0
a:0
开始赋值
a:0
a:1
结束循环

去网上搜了一下发现这么一段话:

When thread A writes to a volatile variable and subsequently thread B reads the same variable, the values of all variables that were visible to A prior to writing to the volatile variable, become visible to B after reading the volatile variable.

大致理解了一下:thread A对volatile 变量进行写入,随后thread B读取此变量的值,在A写入之前,所有值对A是可见的(即A直接访问的是(进程的)内存,不是自己线程的内存区域),在B读入之后,对B是可见的。因此看这种情况应该是因为先读了b,所以之后的a会对更新变量可见。而先读a,则这次循环没能成功,但是等到读了b,下一次循环就能成功,这就是为什么两次结果会多循环一次的原因。

最后,重申一下volitale使用原则:

  1. 对变量的写操作不依赖于当前值。
  2. 该变量没有包含在具有其他变量的不变式中。
推荐问题
宣传栏