并发编程----6、Volatile关键字

目录

 

一、简介

二、JMM与CPU

三、Volatile的语句分析

四、Volatile使用场景

五、volatile与synchronized的区别


一、简介

synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁。而volatile就可以说是java虚拟机提供的最轻量级的同步机制。但它同时不容易被正确理解,也至于在并发编程中很多程序员遇到线程安全的问题就会使用synchronized。

被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。当然这是在多线程的情况下,单线程没啥意义。

volatile也能解决数据不一致问题,主要表现在内存层级上,大家继续往下看。

二、JMM与CPU

我们在第一节JMM中介绍过CPU的缓存模型,我们先回顾下

如下图所示,因为每个cpu有自己独立的寄存器和缓存,但是内存是共享的,所以会存在内存不一致性问题。

大部分一致性解决方案就是:

1、总线枷锁 (粒度太大)

2、MESI 缓存一致性协议,基于cache line 实现

       a.       读操作:不做任何事情,把Cache中的数据读到寄存器

       b.       写操作:发出信号通知其他的CPU将该变量的Cache line置为无效,其他的CPU要访问这个变量的时候,只能从内存中获取。

 

JAVA内存模型描述:

1)       主存中的数据所有线程都可以访问(共享数据)

2)       每个线程都有自己的工作空间,(本地内存)(私有数据)

3)       工作空间数据:局部变量、内存的副本

4)       线程不能直接修改内存中的数据,只能读到工作空间来修改,修改完成后刷新到内存

 

这个模型结构大家现在是不是已经深入刻在脑子里面了?

使用了Volatile修饰的变量,被一个线程修改了以后,另外一个线程在读取到这个变量的时候就是最新的值,所以才会成为可见性,Volatile可以理解成为就是在JMM上实现了类似MESI协议。MESI在硬件层级上是使cache line失效。Volatile就是在JMM上实现变量修改后,其他线程马上感知到。怎么感知的呢?cpu的总线会将信息传递给各个线程。

三、Volatile的语句分析

Volatile的作用: 就是让其他的线程能够马上感知到某一线程对某个变量的修改。

volatile主要保证2个特性,可见性(多个现场见自己的工作空间里面的数据保持一致)和有序性不保证原子性

1、保证可见性:

对共享变量的修改,其他的线程马上能感知到;

不能保证原子性,如果N个线程都从内存取出值到工作内存,修改后,又一起写回去。就会出现多次覆盖。因为失效是工作空间里面的失效了。但是如果都已经处于写回状态时,就没办法了,会多次一样的值覆盖。

2、保证有序性:

编辑器优化和指令重排。

重排序(编译阶段、指令优化阶段);

输入程序的代码顺序并不是实际执行的顺序;

重排序后对单线程没有影响,对多线程有影响;

 

对于Volatile修饰的变量 在重排序的时候有如下规则:

1)volatile之前的代码不能调整到它后面

2)volatile之后的代码不能调整到它之前

3)volatile修饰的代码位置不变

例如:

Int i=0;

Int a=3;

Int b=5;

Volatile Int j=3;

顺序重排后不能出现这样的情况。

Volatile Int j=3;

Int i=0;

Int a=3;

Int b=5;

 

volatile的实现就是加了一个锁:LOCK。

通过javap 命令,将字节码文件反编译。观察反编译的结果,对于volatile修饰的变量,发现反编译得到的字节码并没有什么帮助,和不加volatile修饰的变量没有任何区别。也就是说,字节码层面volatile变量并没有什么不同。

下面通过查看Java的汇编指令,查看Java代码最真实的运行细节。

大家可以在网上找个hsdis-amd64.dll 然后放到jdk文件夹下面/jre/bin/server中,然后再执行的时候加上:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

 执行就可以看到字节码了。这个lock cpmxchg 就好比是一个栅栏一样,使得上面的指令无法到下面去,下面的指令无法到上面去,达到了有序性的目的。

四、Volatile使用场景

 

1、状态标志(开关模式)

代码如下:

public class ShutDowsnDemmo extends Thread{
    private volatile boolean started=false;
 
    @Override
    public void run() {
        while(started){
            dowork();
        }
    }
    public void shutdown(){
        started=false;
    }
}

 

2、双重检查锁(double-checked-locking) 

常见到的是单例模式,下节我们在主要介绍单例模式。

public class Singleton {
    private volatile static Singleton instance;
    public static Singleton getInstance(){
        if(instance==null){
            synchronized (Singleton.class){
                instance=new Singleton();
            }
        }
        return instance;
    }
}

 

3、需要利用顺序性(防止被指令重排)

public class VolaitleDemo1 {
    private static volatile int m=0;
    public static void main(String[] args) {
        int i=0,j=0;
        i++;
        m++;
        j=i+m;
        System.out.println("last value : "+j);
    }
}

五、volatile与synchronized的区别

 

1、使用上的区别

Volatile只能修饰变量;

synchronized只能修饰方法和语句块;

2、对原子性的保证

synchronized可以保证原子性;

Volatile不能保证原子性;所以不要用来做值增加等操作,当开关还是可以的。

3、对可见性的保证

都可以保证可见性,但实现原理不同

Volatile对变量加了lock;

synchronized使用monitor 的 monitorEnter和monitorexit  或者 信号量

4、对有序性的保证

Volatile能保证有序;

synchronized可以保证有序性,但是代价太大(重量级锁)并发退化到串行,所以不要在代码块里面写太多的业务代码。

5、阻塞

synchronized引起阻塞

Volatile不会引起阻塞

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值