JVM volatile 错误理解和正确理解

可见性错误理解

   public class TestVolatile extends Thread {

    private static boolean test = true;

    public static void main(String[] args) throws Exception {
        new TestVolatile().start(); // 开始执行子线程的循环

        Thread.sleep(100); // 保证循环代码运行次数足够多,触发JIT编译
        test = false;
    }

    public void run() {
        while (test) { }; // 死循环
    }
}

错误理解: 因为while(test)代码块中没有代码或没有用到test引发Unreferenced,所以编译器将它编译成了while(true),不然为什么加入System.out.println(test);后就跳出循环了,肯定是用到了这个变量。

打脸操作:来看字节码被编译成什么了

public class cn.test.TestVolatile extends java.lang.Thread {
public cn.test.TestVolatile();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Thread."<init>" : ()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class cn/test/TestVolatile
3: dup
4: invokespecial #3 // Method “<init>” : ()V
7: invokevirtual #4 // Method start:()V
10: ldc2_w #5 // long 100l
13: invokestatic #7 // Method java/lang/Thread.sleep:(J)V
16: iconst_0
17: putstatic #8 // Field test:Z
20: return
public void run();
Code:
0: getstatic #8 // Field test:Z
3: ifeq 9
6: goto 0
9: return
static {};
Code:
0: iconst_1
1: putstatic #8 // Field test:Z
4: return
}
Process finished with exit code 0

可以看见是getstatic #8 然后是ifeq的话就到9(return),明显没有被编译成true,打不打脸?

不加volatile也可以跳出循环,不是应该不可见吗?

   public class TestVolatile extends Thread {

    private static boolean test = true;

    public static void main(String[] args) throws Exception {
        new TestVolatile().start(); // 开始执行子线程的循环

        Thread.sleep(100); // 保证循环代码运行次数足够多,触发JIT编译
        test = false;
    }

     public void run() {
        while (test) {
            System.out.println("随便写点东西");
        } // 可以跳出循环
    }
}

为什么可以跳出循环?难道是真的加了代码就一定能跳出循环?加了代码就变成可见的了?

我们将代码块中的代码换一个

   public class TestVolatile extends Thread {

    private static boolean test = true;

    public static void main(String[] args) throws Exception {
        new TestVolatile().start(); // 开始执行子线程的循环

        Thread.sleep(100); // 保证循环代码运行次数足够多,触发JIT编译
        test = false;
    }

     public void run() {
        while (test) {
            boolean flag = test;
        } // 又成了死循环
    }
}

代码块中加了代码啊,而且也用到了test?怎么这次又不跳出循环了?

原因

JVM会尽力保证内存的可见性,即便这个变量没有加volatile关键字。怎么尽力保证呢?
1.在同步方法结束后变量就会被刷回主存
2.用户手动让线程发生重新调度也会让变量变得可见。
3.调用任何一个volatile的读取或写入

至于为什么代码块中的 boolean flag = test; 不能跳出循环,而System.out可以跳出循环的原因在于:

你们用 java 多线程的时候没有发现System.out.println 并不会让输出混乱吗?其他语言这么做的话输出是混乱的,输出可能会变成"东随随西便写点写东西随写",而java并没有同时往输出缓冲区写东西。System.out.println是线程安全的,内部有synchronized,不信你加个 Thread.sleep(0);或Thread.yield();或调用个synchronized方法。

public class TestVolatile extends Thread {

    private static boolean test = true;

    public static void main(String[] args) throws Exception {
        new TestVolatile().start(); // 开始执行子线程的循环
        Thread.sleep(100); // 保证循环代码运行次数足够多,触发JIT编译
        test = false;
    }

	//你可以去掉这个synchronized 测试一下
    public synchronized void sync(){} 
    
    public void run() {
        while (test) {
            sync();
            // synchronized(this){}
            // synchronized(TestVolatile.class){}
            // Thread.currentThread().getName(); //name是volatile的
            // Thread.currentThread().getPriority();//这个就不行,因为priority没有volatile
        } 
    }

}

有兴趣的可以了解一下happends before和四种内存屏障
事实上 如果在多线程状态下,如果某个成员变量同时涉及到了读和写,我们会加入同步代码块或锁之类的方式,所以volatile主要用在下面这类问题上。

DCL 单例模式一定要用到volatile吗

	public class Singleton {
    private volatile static Singleton instance = null;
    public static Singleton getInstance() {
        if(null == instance) {    
            synchronized (Singleton.class) {
                if(null == instance) {                    
                    instance = new Singleton();
                }
            }
        }
        return instance;    
    }
}

错误理解: 因为不加volatile会导致发生指令重排,可能先进行了分配空间,然后instance指向了这块空间,但是其中没有任何实例信息,所以用的时候会出错。
我的理解: 事实上并不是这样,一个对象的初始化在字节码中长这样:

  • new 创建指定类型的对象实例、对其进行默认初始化,并且将指向该实例的一个引用压入操作数栈顶

  • dup 拷贝一份引用 因为下面的初始化会消耗掉操作数栈顶的引用

  • invokespecial init 初始化对象

  • astore1 存储到本地变量

  • return

    也许astore会被重排,其他线程只是判断是否存在instance,所以第一个线程只要触发new\dup\astore时,第二个线程刚好进来直接拿去用就会导致成员变量还未初始化,所以加volatile的原因在于,防止使用还没有进行初始化的对象,意思就是防止使用还没有执行构造函数的对象,如果你的构造函数中没有对成员的赋值操作,你也没有父类,事实上可以不加volatile。

永远不要过分相信别人说的话,自己做实验,人云亦云如果人家说的本身就是错的呢?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没事干写博客玩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值