Volatile关键字

基础知识补充:

主内存和cpu之间的关系,因为cpu是在是处理速度太快了。所以一般cpu都有一个cpu缓存,上图的意思是主内存---》cpu缓存---》cpu寄存器---》cpu执行处理,写的时候反之。

åå­éä¿¡æä½


看上图,就是多线程情况下java操作变量的大致步骤,需要注意的是,多线程操作的变量都是从主内存拿到的是变量的副本,然后进行一系列操作后,另赋值给主内存,所以,这种情况下就会导致多线程情况下数据不一致的问题。

JVM模å

JMM规范了Java虚拟机与计算机内存是如何协同工作的: 规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。

线程不安全问题原因:由于有缓存的存在,遇到对共享变量更改的时候,多个线程同时读取主存中共享变量的值到自己的工作内存中,线程1对共享变量的更改,更新到主存中,线程2并不知情,所以线程2还是使用自己缓存中的数据,进行更改,这样就导致了,数据不一致的问题。

怎么解决呢?

1、volatile关键字

可见性:当对volatile标记的变量进行修改的时候,会将其他缓存中存储的修改之前的变量清除,也就相当于缓存失效,然后重新读取,一般来说应该是先在进行修改的缓存A中修改为新值,然后通知其他缓存清除掉此变量,当其他缓存B中的线程读取此变量时,会向总线发送消息,这时存储新值的缓存A获取到消息,将新值传给B。最后将新值写入内存。当变量需要更新时都是此步骤,volatile的作用是被其修饰的变量,每次更新时,都会刷新上述步骤。

总之相当于缓存失效了,直接对内存进行操作。这样就不会有线程安全问题了。

原子性:volatile变量的单个读/写操作是原子性的且具有可见性,复合操作(依赖当前值的读写复合操作等,比如i++;以及该变量包含在具有其他变量的不变式中)不具有原子性。一般volatile会结合CAS保证原子性和可见性。

每一个线程都有一份与其他线程隔离独立的数据空间叫做工作内存,工作内存一部分存储的是线程私有数据(普通方法区中的数据),一部分是主内存中共享数据的拷贝。

  • 对于普通共享变量:线程持有主内存中共享变量的数据拷贝,当发生读操作时,线程首先在自己的拷贝中查找,如果没有则从主内存中拷贝;发生写操作时,将会修改线程拷贝的数据,而不是主内存中的共享数据,所以无法保证共享变量的可见性。

  • volatile的写内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。和锁synchronized的释放内存语义一致

  • volatile的读内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效。线程接下来将从主内存中读取共享变量。和锁synchronized的获取内存语义一致

java编译器通过在volatile的读写前后插入内存屏障指令(指令重排序不可越过内存屏障)来禁止特定类型的编译器和处理器重排序来实现上述内存语义。

具体为:1) 编译器禁止volatile读与volatile读后面的任意内存操作重排序。2) 编译器禁止volatile写与volatile前面的任意内存操作重排序。

volatile的写-读和锁的释放-获取具有相同的内存语义:volatile的写和锁的释放有相同的内存语义,volatile的读与锁的获取有相同的内存语义。

 

volatile使用场景

volatile不能保证原子性,只能保证可见性。锁synchronized可以保证原子性和可见性。

正确使用 volatile 变量的条件

您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不变式中。

场景一、结合使用 volatile 和 synchronized 实现 “开销较低的读-写锁”

volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。读的时候不加锁,写的时候保证可见性,不会读取脏数据。--轻量级的锁机制。适用于多读少写的情况。

public class CheesyCounter {

    private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {
        return value++;
    }
}

场景二、状态标志

private volatile boolean status = false;
public void shutdown(){ status = true;}
public void doWork(){
          while(!status){
           doTask();
    }
}

把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)

  • 原子性意味着个时刻,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护(这段代码前后会加上monitor start和monitor exit保证原子性)。从而防止多个线程在更新共享状态时相互冲突。 所谓原子性操作是指不会被线程调度机子打断的操作,这种操作一旦开始,就一直到幸运星结束,中间不会有任何切换(切换线程)。
  • 可见性则更为微妙,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

volatile的使用条件:

volatile变量具有 synchronized 的可见性特性,但是不具备原子性。这就是说线程能够自动发现 volatile 变量的最新值

volatile变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值