深入理解与正确使用Java的关键字volatile

    作者:课课家教育更新于: 2017-06-16 14:07:33

    Web开发

      欢迎各位小伙伴阅读本篇文章,今天就来和大家说一说如何“理解与正确使用java的关键字volatile”,有需要的小伙伴,可以参考一下。课课家教育平台提醒各位:本篇文章纯干货~因此大家一定要认真阅读本篇文章哦!

         概述

      java语言中关键字 volatile 被称作轻量级的 synchronized,与synchronized相比,volatile编码相对简单且运行的时的开销较少,但能够正确合理的应用好 volatile 并不是那么的容易,因为它比使用锁更容易出错,接下来本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方。

      为何使用 volatile?

      (1)简易性:在某些需要同步的场景下使用volatile变量要比使用锁更加简单

      (2)性能:在某些情况下使用volatile同步机制的性能要优于锁

      (3)volatile操作不会像锁一样容易造成阻塞

      volatile 特性

      (1)volatile 变量具有 synchronized 的可见性特性,及如果一个字段被声明为volatile,java线程内存模型确保所有的线程看到这个变量的值是一致的

      (2)禁止进行指令重排序

      (3)不保证原子性

      注:

      ① 重排序:重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段

      ② 原子性:不可中断的一个或一系列操作

      ③ 可见性:锁提供了两种主要特性:互斥和可见性,互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。

      volatile 的实现原理

      如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,该Lock指令会使这个变量所在缓存行的数据回写到系统内存,根据缓存一致性协议,每个处理器都会通过嗅探在总线上传输的数据来检查自己缓存的值是否已过期,当处理器发现自己的缓存行对应的地址被修改,就会将当前处理器的缓存行设置成无效状态,在下次访问相同内存地址时,强制执行缓存行填充。

      在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。

      一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。以下例子展现了volatile的作用:

      Java代码

      public class StoppableTask extends Thread {

      private volatile boolean pleaseStop;

      public void run() {

      while (!pleaseStop) {

      // do some stuff...

      }

      }

      public void tellMeToStop() {

      pleaseStop = true;

      }

      }

      假如pleaseStop没有被声明为volatile,线程执行run的时候检查的是自己的副本,就不能及时得知其他线程已经调用tellMeToStop()修改了pleaseStop的值。

      Volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。如果配合Java 5增加的atomic wrapper classes,对它们的increase之类的操作就不需要sychronized。

      正确使用 volatile 的场景

      volatile 主要用来解决多线程环境中内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,就无法解决线程安全问题。如:

      1、不适合使用volatile的场景(非原子性操作)

      (1)反例

    深入理解与正确使用Java的关键字volatile_Web_安全_大数据_课课家教育

      这个方法的目的是要确保每次调用都返回不同的自增值,然而结果并不理想,问题在于增量操作符(++)不是原子操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,如果第二个线程在第一个线程读取旧值和写回新值期间读取这个域,第二个线程与第一个线程就会读取到同一个值。

      (2)正例

      其实面对上面的反例场景可以使用JDk1.5 java.util.concurrent.atomic中提供的原子包装类型来保证原子性操作

    其实面对上面的反例场景可以使用JDK1.5 java.util.concurrent.atomic中提供的原子包装类型来保证原子性操作

      2、适合使用 volatile 的场景

      在日常工作当中volatile大多被在状态标志的场景当中,如:

      要通过一个线程来终止另外一个线程的场景

      (1)反例

    运行后发现该程序根本无法终止循环,原因是,java语言规范并不保证一个线程写入的值对另外一个线程是可见的,所以即使主线程main函数修改了共享变量stopThread状态,但是对th线程并不一定可见,最终导致循环无法终止。

      运行后发现该程序根本无法终止循环,原因是,java语言规范并不保证一个线程写入的值对另外一个线程是可见的,所以即使主线程main函数修改了共享变量stopThread状态,但是对th线程并不一定可见,最终导致循环无法终止。

      (2)正例

     通过使用关键字volatile修饰共享变量stopThread,根据volatile的可见性原则可以保证主线程main函数修改了共享变量stopThread状态后对线程th来说是立即可见的,所以在两秒内线程th将停止循环。

      通过使用关键字volatile修饰共享变量stopThread,根据volatile的可见性原则可以保证主线程main函数修改了共享变量stopThread状态后对线程th来说是立即可见的,所以在两秒内线程th将停止循环。

         结束语:本文通过对volatile的特性介绍,以及volatile的实现原理,最后结合volatile的特性举例说明它在使用过程中应该注意的使用规则,但愿本篇文章对各位小伙伴有所帮助,如果还想了解这方面的只是内容,随时可以登录课课家教育平台进行浏览哦~

课课家教育

未登录

1