并发编程之volatile关键字

volatile的内存语义

volatile是Java虚拟机提供的轻量级的同步机制。volatile关键字有如下两个作用 保证被volatile修饰的共享变量对所有线程总数可见的,也就是当一个线程修改了一 个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。
禁止指令重排序优化。

volatile的可见性

JMM的介绍时已经提到,volatile是通过缓刑一致性协议实现内存可见性的。

volatile的禁止重排优化

volatile关键字另一个作用就是禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象,关于指令重排优化前面已详细分析过,这里主要简单说明
一下volatile是如何实现禁止指令重排优化的。先了解一个概念,内存屏障(Memory Barrier)。 内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译 器和处理器都能执行指令重排优化。如果在指令间插入一Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏 障禁止在内存屏障前后的指令执行重排序优化。Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。总之, volatile变量正是通过内存屏障实现其在内存中的语义,即可见性和禁止重排优化。内存屏障可参考volatile禁止重排序的原理-内存屏障

接下来我们来看一段代码:

public class JMMOderDemo {
    public static int a,b;
    public static int x,y;
    public static void main(String[] args) throws  Exception {
        int count = 1;
        for(;;) {
            a = b = 0;
            x = y = 0;
            Thread t1 = new Thread(new Runnable() {
                public void run() {
                    //waitFor(5);
                    a = 1;
                    y = b;
                }
            });
            Thread t2 = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    x = a;
                }
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();

            if (x == 0 && y == 0) {
                System.out.println("x 值, y值(" + x + "," + y + ")");
                break;
            } else {
                System.out.println("第" + count + "x 值, y值(" + x + "," + y + ")");
            }
            count++;
        }
    }

    public static void waitFor(long s) {
        try {
            Thread.sleep(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

通过上述的代码我们可以得出如下结论,只有以下三种情况:

x y 1,0
x y 0,1
x y 1,1

但是通过测试最终会跑出:

x y 0,0

如图:
在这里插入图片描述
这是为什么呢?之前有提到过就是代码在编译阶段会进行优化从而产生重排序,
我们以线程t1为例:

 a = 1;     // 第1步
 y = b;     // 第2步

在线程t1中,这两段代码可能发生重排序,因为第二步的执行不依赖于第一步的操作,所以可能发生重排序导致在执行过程中实际如下:

y = b;
a = 1;

那么在执行过程中线程t1先执行y=b;然后线程t2再依次执行b=1;x=a;执行完成后再执行a = 1;那么整个过程就会导致x=0y=0;我们之前有结论就是volatile可以禁止指令重排优化,那么我们为a和b添加上volitile关键字,就可以避免这种问题了。

总线风暴引入synchronized

之前我们了解到MESI是通过总线通讯从而保证变量状态的,同理CAS也是基于总线通讯的,那么随着volatile个CAS的过度使用,会大量占用总线的带宽从而影响其他操作,那么为了解决这种问题,从而引入synchronized,当然这样会牺牲并发的效率,但是总线阻塞问题也是要解决的,所以会牺牲并发的性能。(个人理解)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值