Java并发——volatile关键字、有序性浅谈

    volatile关键字是一种比锁机制稍弱点的同步机制,volatile变量会被编译器以及JVM运行时注意到是一个共享变量,被它修饰的变量具有两大特征:第一,保证了不同线程对该变量的可见性。被它修饰的变量在线程中被修改后会立马写入主存中,在Java内存模型中,volatile变量的写入操作与读取操作同时到达的时候,将先进行写入操作,来保证数据的一致性。

    第二点就是阻止了指令重排序,重排序是引起线程间有序性问题的罪魁祸首,简单的描述重排序问题是Java内存模型使得不同的线程看到的操作执行顺序是不一样的。在讨论内置锁的时候我们谈论了线程之间的可见性问题,但事实上更糟的是,线程之间不仅仅有着更改后的数据无法及时被使用者看到,还有指令的在不同线程之间重排的问题,这就导致了不仅在开始时是错的,运行时更是错上加错的结果。也就是说在线程中会发生不按程序顺序执行的指令,本来A在B之后执行,但是因为重排序会造成A在B之前执行。比如示例程序:

public class Test {
    static String a="蕾姆";
    static String b="拉姆";
    static String x="托尔";
    static String y="康娜";
    public static void main (String args[]) throws InterruptedException {
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                a="艾米莉亚";
                x=b;
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                b="艾米莉亚";
                y=a;
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(x+"\n"+y);
    }
}

    这份程序有可能输出的是:拉姆、蕾姆。但是这个结果很难测出来,现在的CPU有些阻止了写写重排序,但是一定要知道有这回事,而且也很难预测这个程序的结果,充满着很多种可能。加了volatile修饰的变量则保证了这种可能不会发生,需要注意的是,重排序不会改变对该指令前面成员有依赖的指令顺序。比如:

int a=10; 1int b=6;  2int c=a+b; 3

    1式和2式可以相互替换,但是3式对前两者有依赖,所以顺序是死的。但是在上一个例子中,a、x等变量也依赖着前面的成员啊?因为每个线程有着自己的单独的内存空间进行存贮与运算,a、x在线程的空间内是主存中的副本,也就是说在这个空间,它们没有依赖项,所以可能发生重排序。这也就是为什么重排序影响不到单线程却能影响到多线程的原因。

    当然,你也可以用锁来实现volatile这两大特性,但是比其的开销要大一些。volatile的一个很明显的缺点就是它没有实现原子性操作,也就是这一点,推荐大家尽可能的少使用volatile。有一点注意的是:

volatile i++;

    这个代码虽然看起来像是原子性的操作,但是其中却包括三个操作:读取i的值,计算i+1的值,写入i的值,所以必须利用锁来保证其原子性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值