volatile可见性的验证,system.out.println和sleep对可见性的影响

可见性验证:

//volatile可见性验证
public class volatileTestone {

    public static void main(String[] args) {
        MyData myData = new MyData();

        //新建kjx线程先睡1s再 将num加了60,然后打印出来
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTo60();
            System.out.println("kjx线程:"+myData.number);

        },"kjx").start();

        //如果main线程检测不到number=60,将会一直循环
        while (myData.number!=60){

        }
        //main线程打印出修改后的num
        System.out.println("main线程:"+myData.number);
    }
}

public class MyData{
    int number = 0;
    public void addTo60(){
        this.number = 60;
    }

}

volatileTestone 的打印结果:

volatile可见性的验证,system.out.println和sleep对可见性的影响_第1张图片

可以看到number没有加volatile关键字时,main线程是不会发现在kjx线程中number已经被修改了。所以一直循环,程序无法结束

 

将number加上volatile关键字

public class MyData{
    volatile int number = 0;
    public void addTo60(){
        this.number = 60;
    }

}

执行结果:

volatile可见性的验证,system.out.println和sleep对可见性的影响_第2张图片

可以看到number加上volatile关键字后,在kjx线程中被改变后,会通知main线程等其他的线程。体现出来volatile可见性的特性。

以上可以验证volatile的可见性。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

其实在验证的过程中也走了很多坑。列如system.out.println,sleep等等。下面记录一下。

由于我习惯用system.out.println输出在控制台观察结果。最开始在while循环里加了sout,结果在没加volatile的情况下,程序竟然停止了!显然system.out.println影响了可见性,也就是main线程已经获取到了kxj线程工作内存中的数据。

        //如果main线程检测不到number=60,将会一直循环
        while (myData.number!=60){
            System.out.println("myData.number:"+myData.number);
        }

点进println方法:

volatile可见性的验证,system.out.println和sleep对可见性的影响_第3张图片

该方法是加锁的synchronized,于是查了下资料

JMM关于synchronized的两条规定:

1)线程解锁前,必须把共享变量的最新值刷新到主内存中

2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值

(注意:加锁与解锁需要是同一把锁)

synchronized具体过程是:

  1. 获得同步锁;
  2. 清空工作内存;
  3. 从主内存拷贝对象副本到工作内存;
  4. 执行代码(计算或者输出等);
  5. 刷新主内存数据;
  6. 释放同步锁。
     

 所以说number变量在这个过程中,已经将工作内存中的值同步到主内存中,main线程再次读取时即可见值已经变了。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

由于之前验证的时候想让while循环在number被改变后在运行,所以在main线程中加了sleep(其实没啥意义,但是却又给自己挖了个坑,给搞懵逼了)。

加完之后,kjx线程中数据是延迟1秒后变化的,所以main线程我就延迟了2s,结果!!!

//volatile可见性验证
public class volatileTestone {

    public static void main(String[] args) {
        MyData myData = new MyData();

        //新建kjx线程先睡1s再 将num加了60,然后打印出来
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTo60();
            System.out.println("kjx线程:"+myData.number);

        },"kjx").start();

        //延迟两秒后再循环!!!
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //如果main线程检测不到number=60,将会一直循环
        while (myData.number!=60){
            System.out.println("myData.number:"+myData.number);
        }

        //main线程打印出修改后的num
        System.out.println("main线程:"+myData.number);
    }
}

又正常结束了。。。明明就只是加了个延迟,为啥kjx线程中的操作在main线程又可见了呢?

于是又去查资料

实际上,JVM对于现代的机器做了最大程度的优化,也就是说,最大程度的保障了线程和主存之间的及时的同步,也就是相当于虚拟机尽可能的帮我们加了个volatile,但是,当CPU被一直占用的时候,同步就会出现不及时,也就出现了后台线程一直不结束的情况。在线程中做一些耗时但不耗CPU的操作,也会结束的很快,因为这个时候CPU空闲了,JVM就有机会尽快的将主存和栈变量同步

 

 

 

你可能感兴趣的:(java后端)