volatile详解(一)(可见性)

2021-08-29  本文已影响0人  iMikasa_

再介绍volatile之前,我们了解一下有什么状况,让我们需要用到volatile。

这就要说起导致线程安全的——可见性了。
举个例子

//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;

假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。

此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.

这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。

下面是展现问题的具体代码

package com.imikasa.test1;

public class TestThread2 {
    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(flag){
//                System.out.println("hello");
            }
        }).start();
        System.out.println("1s后停止线程");
        Thread.sleep(1000);
        flag = false;
    }
}

程序运行之后会一直运行是循环,主线程修改的flag的值不会被其他线程知道(cpu缓存引起),可见性问题通常都是由一个线程修改共享变量,其他线程读取那个共享变量引起的...

如何保证可见性

Java提供了volatile关键字来保证可见性。

当一个共享变量被volatile修饰时static volatile boolean flag = true;,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

值得注意的是,在上面我们测试展示可见性问题的代码中,我们把死循环中的打印语句注释去掉,在运行,发现一秒后,死循环结束,保证了可见性,这是为什么呢?
原因很简单,我们看println()的源码就知道了


println()

因为通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。但是缺点是synchronized属于重量级操作,性能相对更低

上一篇 下一篇

猜你喜欢

热点阅读