volatile的又一些理解

                                                  Java的线程开发之volatile关键字的又一些理解

提到java中的volatile,大家肯定会说,这个关键字啊,用它来修饰的变量对其他的线程是立即可见的。哈哈,不错不错,这个关键字的确具备这个功能,但是如果说基于volatile变量的运算在并发下是线程安全的,那么这句话就有待商榷了。

“从物理存储的角度看,各个线程的工作内存中volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题,但是java里面的运算操作符并非原子性,这导致volatile变量的运算在并发下一样是不安全的。”

package com.xh.lesson;

public class VolatileTest {
    public static volatile int race = 0;

    public static void increase(){
        race++;
    }
    private static final int THREADS_COUNT = 20;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <10000 ; j++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount()>2) {
            Thread.yield();
        }
        System.out.println(race);
    }

}

上面的这段代码发起了20个线程,每个线程对race变量进行10000次自增操作,如果这段代码能够正常并发的话,最后输出的结果应该是200000.

看到这里,有些读者可能会有些疑问了,代码下面的那张大图的运行结果不就是200000吗,下面的几张小图都小于200000.

说实话,当我第二次运行代码时出现了这个结果也令我很吃惊(当时我就觉得我TM是不是就是传说中的天选之子!!),因为我之后有运行了几十次,发现每次都是小于200000的,我就又觉得我啥也不是了==。

好了,言归正传,下面我们来看一下使用Javap反编译这段代码后得到的代码清单

public static void increase();
    Code:
        Stack=2,Locals=0,Args_size=0
        0: getstatic
        3:iconst_1
        4:iadd
        5:putstatic
        8:return
    LineNumberTable:
        line 14: 0 
        line 15: 8

从字节码层面上分析:当getstatic指令把race的值取到操作栈顶时,volatile关键字保证了race的值在此时是正确的,但是在执行iconst_1、iadd这些指令的时候,其他线程可能已经把race的值改变了,而操作栈顶的值就变成了过期的数据,所以putstatic指令执行后就可能把较小的race值同步回主内存中。

这样分析下来,上面等于200000的情况是说明每个线程对race变量执行指令的时候,其他的线程都未改变race的值。由此看来,说我是天选之子也不为过嘛!

还有一点,使用字节码来分析并发问题仍然是不严谨的,因为即使编译出来只有一跳字节码指令,也并不意味执行这条指令就是毅哥原子操作。一条字节码指令在解释执行时,解释器都要运行许多行代码才能实现它的语义。如果是编译执行,一条字节码指令也可能转化成若干条本地机器码指令。此处使用-XX:+ PrintAssembly 参数输出反汇编来分析才会更加严谨一些。

由于volatile变量只能保证可见性,在不符合一下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized、java.util.concurrent中的锁或原子类)来保证原子性:

1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

2、变量不需要与其他的状态变量共同参与不变约束。

还是上面那个代码,在increase()方法上添加 synchronized 关键字即可保证原子性。

以上摘录至《深入理解Java虚拟机》,想要学习JVM的值得一读!

后续还会更新!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值