volatile和线程安全、原子性、有序性、可见性

一、线程安全要考虑三个方面:可见性、有序性、原子性

        ①可见性指,一个线程对共享变量修改,另一个线程能看到最新的结果

        ②有序性指,一个线程内代码按编写顺序执行

        ③原子性指,一个线程内多行代码以一个整体运行,期间不能有其它线程的代码插队

二、volatile 能够保证共享变量可见性与有序性,但并不能保证原子性

        ①原子性举例

                下面代码有一个共享变量balance=10,有两个线程一个-5,一个+5,后台打印出来是10。但如果运行上万次,就有可能不是这个结果。这样的代码虽然只有一句,并不是原子性的操作,我们从控制台打开,运行javap -p -v AddAndSubtract.class,查看他的字节码,发现相加方法或者相减方法的代码是多个字节码指令组成的。

public class AddAndSubtract {

    static volatile int balance = 10;

    public static void subtract() {
        int b = balance;
        b -= 5;
        balance = b;
    }

    public static void add() {
        int b = balance;
        b += 5;
        balance = b;
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        new Thread(() -> {
            subtract();
            latch.countDown();
        }).start();
        new Thread(() -> {
            add();
            latch.countDown();
        }).start();
        latch.await();
        LoggerUtils.get().debug("{}", balance);
    }
}

        如下,cpu是在线程中高速切换的,如果发生以下交叉,输入的值是一个错误的结果

/**
 t1 10
 0: getstatic 读取静态变量
                 t2
                 0: getstatic 10 读取静态变量
                 3: iconst_5 准备数字5
                 4: isub  相减
                 5: putstatic 设置静态变量
                    5
 3: iconst_5 准备数字5
 4: iadd 相加
 5: putstatic 设置静态变量
 15

 */

        ②可见性举例

                以下代码foo方法检测stop的值,如果一直为假则i不断相加,设置一个线程,在程序100ms后把stop设置为true,看是否能够成功停止。

public class ForeverLoop {
    static volatile boolean stop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            get().debug("modify stop to true...");
        }).start();

        foo();
    }

    static void foo() {
        int i = 0;
        while (!stop) {
            i++;
        }
        get().debug("stopped... c:{}", i);
    }
}

                从下面运行图得知,stop已经设置,但程序并没有停止。 

                 我们先来看看网上的一个分析,这个说法是错误,这个解释是说变量的值并没有同步到内存中,那我们在写一个线程,在程序运行的0.2s后读取stop的值。

                我们发现线程1是读取到了stop修改后的值,说明还是已经存入到内存,不然它是不会读取到的,他上图的说法不攻自破了。我们来详细解释一下这个可见性问题 

                

                 所有的程度都交给cpu执行,cpu去查看线程1代码里的stop值是什么,根据他的值决定下一步操作,他到物理内存中读到stop的值,第一次他读到的是false,他不断的高速循环,经过测试,0.1s这个读取次数到达上万次,这么多次到物理内存都是false,内存的读写效率是比较低的,这时候JVM里面java即时编译器JIT,他负责代码的优化,任何一条java代码都会翻译成字节码指令,但是他还不能直接交给cpu执行,他还有一个解释器组件,他会将字节码逐行翻译成机器码,再交给cpu执行。JIT来对一些热点的字节码进行优化,反复进行的代码就是热点代码,这个循环次数超出他的优化阈值,他对这个while循环进行了一个很大的优化,直接将stop替换成了false。那问题就来了,线程2改了stop的值,线程1完全不知情,因为机器码已经被替换了。 

        ③有序性举例

                通过第三方插件进行数亿次的压力测试,来体现这个效果。actor1是线程1给xy复制,而actor2是线程2读取xy值。他的值可能有很多种组合,看控制台输出结果。 

        volatile不同位置会影响压力测试结果,volatile相当于一个内存屏障,让他上面的代码无法排到他下面去,读取x的屏障也不能越到读取上去y

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悠哉iky

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

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

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

打赏作者

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

抵扣说明:

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

余额充值