神奇的volatile

什么是volatile?

  打开google,百度一下,你就知道~

  java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。

  说的通俗一点,volatile能帮助我们实现一个重要的特性:保证在多线程处理下线程对资源修改的及时感知;说到volatile就一定会提及JMM内存模型和多线程任务并发场景。

  

  线程中共享的内存,其实并不是全部作为主内存使用的,每个线程对应有自己的堆栈内存,那么我们平时写一个thread,多线程测试会发现,一个共享变量在经过修改后还是会被其他线程所感知,这是为什么呢?

  这里就要说到cpu相关的硬件结构了,最早如果单核cpu单线程,完全不需要考虑这种情况,反正从头到尾都是你自己玩,想玩到天荒地老也不会有人管你。

  现在的服务器标配多核多线程,如果我有一个并发任务去处理,这时候我们并没有办法控制哪个任务会在哪个cpu切片上,具体的cpu如何分配线程,我们以后再说 !

  我们可以看一段代码,这种写法在并发下会有问题:

i = i + 1;i ++

  为什么说这种方式会有并发问题呢,我们知道并发的三个特点:

  1. 原子性:指一个操作是不可中断的,只能一次性执行完,一个操作一旦开始就不 会被其他线程影响
  2. 可见性:如果一个线程修改了资源,其他线程不可见,我们就会说它是存在线程安全问题
  3. 有序性:多线程无法保证顺序,在没有相关依赖关系时,会有指令重排的问题

  在jvm中,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围,就需要使用同步块或锁,顾名思义,上面的代码不是原子操作,它其实是将i的值取出来、再操作自增或者增加,再写入,这就是一个比较常见的并发问题:两个线程同时做i++操作,第一个线程先获取到了i的值,给i加1,这时候再写入,但是主内存还并没有将变量的值同步;这时第二个线程来获取i的值(这时候其实获取的是i的脏数据,并没有做完i++动作),它又将i加1后写入;

  针对这种情况,jvm帮我们提供了AtomicReference可以完成自增或者自增数之类的操作,它的底层是由指令实现的,可以避免这种情况,针对AtomicReference我们以后再详细聊聊。

有了volatile,它能帮助我们做什么?

  有了volatile,它最大的特点是,将一个共享变量及时刷新到共享内存中去,使其他线程也可以立即感知到它的变化。

  其实在jvm,字节码文件中,jvm对加了关键字volatile的变量,在变量修改后立即同步主内存并刷新到共享内存中,其他线程可以立即感知

实例
boolean isRun = false;
while(!isRun){
   //doThings
}

isRun = true;


  上面这种自旋唤醒执行的条件,只有当isRun为true时才会执行,上面两步可以分为两个线程执行,如果第二个线程执行了isRun = true的条件,但是主内存并没有同步到共享内存中,线程1是无法感知的,它还会去执行直到主内存同步完成。

  同时,volatile还能够在一定情况下保证有序性:

    volatile可以禁止指令重排,指令重排的意思就是,jvm在优化执行过程时,对没有依赖的相关代码,并不会完全按照代码的顺序执行:

实例
 int i = 0;               
boolean flag = false;
i = 1;                //语句1   
flag = true;          //语句2

  语句1和语句2,我们不能保证肯定是语句1先执行语句2后执行,这就是指令重排的意思,flag和i并没有相互依赖的东西,jvm认为它们重排并不会影响最终结果(思路有点像存储读写分离的保证最终一致性)

摘自//blog.csdn.net/qq_42569136/article/details/123219118
 private static ObjInstance instance;
//线程1
if(instance!=null){
    instance.method();
}
//线程2
if(instance==null){
    synchronized (ObjInstance.class){
        if(instance==null){
            instance = new ObjInstance();
        }
    }
}

  这种情况,就是指令重排后会导致出现执行顺序的问题

我们应该在什么时候使用volatile?

synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性

posted @ 2022-09-19 17:15  青柠_fisher  阅读(35)  评论(0编辑  收藏  举报