一文读懂volatile

## 一文读懂Volatie

在进入正文以前,先来一段demo代码啊:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Aobing a =new Aobing();
        a.start();
        for(;;){
    
    
            if(a.isFlag()){
    
    
                System.out.println("冲冲冲");
            }
        }
    }
}
class Aobing extends Thread{
    
    
    private boolean flag =false;
    public boolean isFlag(){
    
    
        return flag;
    }
    @Override
    public void run(){
    
    
        try{
    
    
            Thread.sleep(1000);
        }catch (InterruptedException e){
    
    
            e.printStackTrace();
        }
        flag =true;
        System.out.println("flag="+flag);
    }
}

​ 在主程序中,有一个死循环来监控flag的状态,当状态变为true时输出冲冲冲,按理说只要子线程中打印出flag的状态来,冲冲冲就会被打印出来。

​ 但是在实例运行过程中是不会输出冲冲冲的。那又是为什么呢

JMM

​ 在解决这个问题之前我们先来看一下JMM

  • JMM:Java内存模型,是java虚拟机规范中所定义的一种内存模型,java内存模型是表转化的,屏蔽掉了底层不用计算机的区别

现代计算机的内存模型

​ 由于现在存储设备与处理器的运算速度差距太大,所以需要加入高速缓存(cache)来作为内存与处理器之间的缓冲。这样处理器无需等待缓慢的内存读写了

​ 虽然高速缓存很好的解决了处理器与内存的速度矛盾,但是也引入了一个新的问题:缓存一致性。

​ 在多处理器系统中,每个处理器都有自己的告诉缓存,而它们又共享一个主内存

JMM

JMM:java内存模型,描述了java查询中各种变量(线程共享变量)的访问规则,以及在JVM中将变量,存储到内存和从内存中读取变量的底层细节

JMM规定:
  • 所有的贡献变量都存储于主内存

    这里的变量指的时候实例变量和类变量,不包含局部变脸,因为局部变量是线程私有的,因此不存在竞争问题

  • 每一个现在还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本

    线程对变量的所有的操作(读、取)都必须在工作内存中完成,而不能直接读写主内存中的变量

  • 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中专来完成

由于这种机制才导致可见性问题的存在,下午就讨论有关可见性的解决方案。

可见性的解决方案

加锁

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Aobing a =new Aobing();
        a.start();
        for(;;) {
    
    
            synchronized (a) {
    
    
                if (a.isFlag()) {
    
    
                    System.out.println("冲冲冲");
                }
            }
        }
    }
}
为啥加锁能解决可见性问题

​ 因为当某一个线程进入Synchronized代码块前后,线程会获得锁,清空工作内存,从主存拷贝共享变量最新的值放到工作内存称为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁

​ 而获取不到锁的线程会阻塞等待,所以变量的值肯定一直都是最新的。

Volatile修饰共享变量

class Aobing extends Thread{
    
    
    private volatile boolean flag =false;
    public boolean isFlag(){
    
    
        return flag;
    }
    @Override
    public void run(){
    
    
        try{
    
    
            Thread.sleep(10);
        }catch (InterruptedException e){
    
    
            e.printStackTrace();
        }
        flag =true;
        System.out.println("flag="+flag);
    }
}
volatile作用

​ 每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,如果他操作了数据并且回写了,这样其他已经读取了数据的线程的变量副本就会失效了。

​ volatile保证了不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即能看到最新的值。

为了解决一致性问题,需要各个处理器访问缓存时都遵循一些协议,读写时要根据协议来进行操作,如:MSI、MOSI、Synapse、Firefly以及DragonProtocol

MESI(缓存一致性协议)

​ 当CPU写数据时,如果发现操作的变量是共享变量,会发出信号通知其他cpu将改变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,会发现是无效的,就会重新获取

嗅探:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

内存屏障

​ java编译器会在生成指令系列时在适当的位置会插入内存屏障来禁止特定类型的处理器重排序

​ volatile变量的可见性是基于内存屏障实现

​ 为了性能优化,JVM会在不改变正确语义的前提下,会运行编译器和处理器对指令序列进行重排序。而JVM提供了内存屏障阻止了这种重排序。

​ java编译器会在生成指令系列时在适当的位置会插入内存屏障指令类禁止特定类型的处理器重排序。

​ 需要注意的是:volatile写 是在前面和后面分别插入内存屏障,而volatile读 是在后面插入两个内存屏障

volatile和synchronized的区别

  • volatile只能修饰实例变量和类变量,而Synchronized可以修饰方法以及代码块
  • volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);Synchronized是一种排他(互斥)机制。
  • volatile用于禁止指令重排序,可以解决单例双重检查对象初始化代码执行乱序问题。
  • volatile可以看做是一个轻量级的Synchronized,volatile不保证原子性(不保证线程执行的有序性),但是如果是对一个共享变量进行多个线程的赋值,没有其他的操作,就可以用volatile来代替Synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就可以保证线程安全了

volatile总结

  • 禁止进行指令重排序(实现有序性)

  • 可见性:volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如booleanflag;或者作为触发器,实现轻量级同步。

  • volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以说它是低成本的。

  • volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。

  • volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主 存中读取。

  • volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。

  • volatile可以使得long和double的赋值是原子的。

  • volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性。

最后

  • 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
  • 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
  • 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。

猜你喜欢

转载自blog.csdn.net/issunmingzhi/article/details/107247438