谁能真正整明白java volatile 关键字? 一知半解的莫入!

[code="java"]
/**

  • VolatileTest.java *
  • Copyright ekupeng,Inc. 2012
    */ package test;

/**

  • @ClassName: VolatileTest
  • @Description: Volatile测试
  • @author Emerson emsn1026@gmail.com
  • @date 2012-11-29 下午06:57:44
  • @version V1.0
  • */
    public class VolatileTest extends Thread {

    // 非volatile标志
    private static boolean flag1 = false;
    // volatile标志
    private static volatile boolean flag2 = false;

    private int i = 0;

    public void run() {
    //Object o = new Object();

    //synchronized (o) {
        /*
         * 注释1
         */
        while (!flag1) {
            i++;
                            //注意 : System.out.println(i);
            /*
             * 注释2
             */
            if (flag2) {
                System.out.println("over:" + i);
                break;
            }
        }
    //}
    

    }

    public static void main(String[] args) {

    VolatileTest t = new VolatileTest();
    t.start();
    
    try {
        Thread.currentThread().sleep(2000);
        // 先更改flag1
        t.flag1 = true;
        /*
         * 注释3
         */
        Thread.currentThread().sleep(1000);
        // 将flag2置为true,如果有机会进入if(flag2),则将退出循环
        t.flag2 = true;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    }

}
[/code]

因为预览时发现大段的注释会自动换行,影响阅读代码,所以我将代码中的注释提取出来:
注释1
外围标志flag1为非volatile,该线程(t)跑起来后由另一线程(main)将flag1改为true后,如果出现情况1.flag1如果不从主存重新读取,那他将继续以false运行,所以会继续循环并进入内部的flag2的if判断;如果出现情况2.flag1从主存重新读取,那他将以true运行,所以会跳出循环,也就没有机会进入flag2的if判断了;
注释2
如果出现情况1,将进入该判断,内部标志flag2为volatile,当线程(main)将flag2改为true后,因为flag2会从主存重新读取,将以true运行,所以将跳出循环,并打印"over"语句
注释3
为了确保flag1的变更有机会被t察觉,并保证flag2能在flag1变为true后进行一次以上while(!flag1)条件判断后再判断if(flag2),sleep1秒(1秒可以跑很多循环了)

以上是我为了说明volatile的功能写的一段程序,目的是想说一个线程1在循环中通过非volatile的布尔变量来进行条件判断,即使在另一个线程2中修改了该布尔变量,由于该线程1的代码执行得到了某种性能优化,不会从主存重新读取布尔值,导致进入死循环,直到内部的volatile布尔值被改变才跳出。

我的问题是:原本我以为会像预想那样的输出“over”语句,这样也说明了volatile的用处。但是我尝试了Sun JDK1.6,1.5,1.4,1.3,1.2(因为volatile是针对jit带来的优化,所以1.2之前的版本就没有尝试)之后发现只有1.2下才会看到该程序对于volatile的演示效果,输出了“over”语句。其他的都只是在外围的while(!flag1)中实时察觉flag1的变化并跳出了循环。原本以为是hotspot的问题,但是我尝试了hotspot的server或client,以及1.3的classic,都是没有效果的,只有1.2才能看到volatile的演示效果。哪位大神给细说下这个情况?

ps:Object那把锁没有实质的意义,只是进出synchronized块时会重新从主存同步数据,我当时随手写了测了下,所以大家可以不考虑,我暂且将它注掉吧....为了说明这一点,我加了行代码:
//注意 : System.out.println(i);
这行代码要是去掉注释,volatile在JDK1.2下也将失去作用,因为System.out.println中含有同步块,一执行该方法,变量将从主存中重新读取。

7个回答

以上是我为了说明volatile的功能写的一段程序,目的是想说一个线程1在循环中通过非volatile的布尔变量来进行条件判断,即使在另一个线程2中修改了该布尔变量,由于该线程1的代码执行得到了某种性能优化,不会从主存重新读取布尔值,导致进入死循环,直到内部的volatile布尔值被改变才跳出。
你可能对JMM的理解有偏差。JMM只会说确保一些happen-before。但是对那些非happen-before的情况也不会出现你这种极端情况吧。

即使在另一个线程2中修改了该布尔变量,由于该线程1的代码执行得到了某种性能优化,不会从主存重新读取布尔值,导致进入死循环。

多线程编程的问题为什么难以发现,是因为那些非happen-before的代码在大多数情况表现的都是happen-before。如果按你这么说,那些不符合happen-before的问题立马就会出现那就好了。

cpszgy
cpszgy 对于volatile的一些hp的现象还需要加上hp的传递性来看。hp(a,b) hp(b,c) 那么hp(a,c). 在jdk1.5时似乎修复了volatile无法表现出传递性这一BUG。 在给i赋值的线程中hp(write i=3,write flag =true).由于volatile的hp关系hp (write flag=true,read flag=true) 在读i的线程中(read flag ,read i).所有hp(write i=3, read i)
大约 7 年之前 回复
cpszgy
cpszgy 你这个涉及到2个问题一个是JVM对循环的优化,一个是JMM。本地测试,也是Server版本,1.6.没有出现你的这种死循环现象。这种基于JVM优化而出现的情况不能一概而论。不同的JVM版本,不同的供应商对循环优化的方式又不一样。在我看java并发编程实践时也看到过这种while循环优化的段落,但是我也没试验出这种死循环的结果。而volatile字段的作用就是jmm会确保该字段的happen-before。而且似乎在不同版本对volatile字段的重排序的保证似乎又不一样。 例如在某个线程: i=3 flag=ture 第2个线程: if(flag){ sysout(i) } 假设flag是volatile字段。i的初始值是1.似乎在1.5之前版本可能第二个线程得到的i有可能是1.这种由于重排序导致的问题。但是在1.5版本屏蔽了volatile指令的重排序(周志明写的深入JAVA虚拟机第5章。但是在java并发编程实践中并无该描述。)在java并发编程实践 中对volatile的happen-before只有这样的描述对volatile域的写入操作happens-before于每一个后续对同一域的读操作。
大约 7 年之前 回复
nimasike
温故而知新666 设置为Server模式 循环不会退出 说明线程VT确实是 从线程栈内存取值。
大约 7 年之前 回复
nimasike
温故而知新666 public class VolatileTest extends Thread { boolean flag = false; int i = 0; public void run() { while (!flag) { i++; } } public static void main(String[] args) throws Exception { VolatileTest vt = new VolatileTest(); vt.start(); Thread.sleep(2000); vt.flag = true; System.out.println("stope" + vt.i); } }
大约 7 年之前 回复
nimasike
温故而知新666 你看看我写的http://www.cnblogs.com/daxin/p/3364014.html 我找到原因了 是因为我的JVM启动设置为 Client 如果设置为Server就看出效果了 因为Client模式没有优化!
大约 7 年之前 回复

关键字:主内存,工作内存

主内存和工作内存的区别,当对变量做非原子性操作时,线程修改了工作内存中的变量内容并不会立刻把结果同步到主内存中,如果加了此关键字就会立刻同步。

nimasike
温故而知新666 兄弟 你说的好像不对!
大约 7 年之前 回复
mengqingyu21
mengqingyu21 这样可以保证其他线程读到的主内存数据是最新的
大约 7 年之前 回复

JMM会确保volatile的改变对别的线程立马可见。
但是JMM又没确保非volatile的改变不会对别的线程立马可见。
所以我没看懂[code="java"] t.flag1 = true;

/*
* 注释3
*/

Thread.currentThread().sleep(1000); [/code]
这一段的用意。
你这么一用那么另外个线程怎么可能不会对flag1的变化不可见呢。都睡了1S了。也该让别的线程察觉了吧。那循环都跳出了。怎么会进入到flag2的循环呢。。。

nimasike
温故而知新666 我上面的例子写的确实有些问题。我现在整明白了。看看我的博客 http://www.cnblogs.com/daxin/p/3364014.html
大约 7 年之前 回复

volatile保证一个线程对变量修改之后其它线程立即可见
如果不用volatile修改,所有对象成员属性发生的修改只有在线程结束后其它线程才能发现这个修改

但是volatile不保证线程安全,所以对volatile变量执行++ -- += -= *= /= 和其它运算后再赋值的操作,可能会出现数据不一致现象。

重要的一点:原子操作

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐