文档章节

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

FEINIK
 FEINIK
发布于 2017/06/04 22:16
字数 1224
阅读 9K
收藏 221

概述

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指令会使这个变量所在缓存行的数据回写到系统内存,根据缓存一致性协议,每个处理器都会通过嗅探在总线上传输的数据来检查自己缓存的值是否已过期,当处理器发现自己的缓存行对应的地址被修改,就会将当前处理器的缓存行设置成无效状态,在下次访问相同内存地址时,强制执行缓存行填充。

正确使用volatile的场景

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

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

(1)反例

​private static volatile int nextSerialNum = 0;
public static long generateSerialNum() {
   return nextSerialNum++;
}

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

(2)正例

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

private static AtomicInteger nextSerialNum = new AtomicInteger(0);
public static long generateSerialNum() {
   return nextSerialNum.getAndIncrement();
}

2、适合使用volatile的场景

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

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

(1)反例

private static boolean stopThread;
public static void main(String[] args) throws InterruptedException {
   Thread th = new Thread(new Runnable() {
      @Override
      public void run() {
         int i = 0;
         while (!stopThread) {
            i++;
         }
      }
   });
   th.start();
   TimeUnit.SECONDS.sleep(2);
   stopThread = true;
}

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

(2)正例

private static volatile boolean stopThread;
public static void main(String[] args) throws InterruptedException {
   Thread th = new Thread(new Runnable() {
      @Override
      public void run() {
         int i = 0;
         while (!stopThread) {
            i++;
         }
      }
   });
   th.start();
   TimeUnit.SECONDS.sleep(2);
   stopThread = true;
}

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

总结

本文通过对volatile的特性介绍,以及volatile的实现原理,最后结合volatile的特性举例说明它在使用过程中应该注意的使用规则,好了,希望本文对您有所帮助!

 

© 著作权归作者所有

FEINIK
粉丝 227
博文 61
码字总数 61705
作品 0
广州
高级程序员
私信 提问
加载中

评论(22)

FEINIK
FEINIK 博主

引用来自“侠客执剑行”的评论

楼主我要转载,本来想自己写的,看到你写了我就懒了

@侠客执剑行 可以转载的!
侠客执剑行
侠客执剑行
楼主我要转载,本来想自己写的,看到你写了我就懒了
_森屿海巷_
_森屿海巷_
好文
FEINIK
FEINIK 博主

引用来自“thbcd”的评论

volatile只能保证变量加载到线程内存时的值是最新,当一个线程抢先修改了变量但是还没执行完的时候,其他线程还是不知道变量被修改的,所以企图拿volatile做同步就是错,老老实实用lock吧

@thbcd 你所说的是互斥性,我所讲的volatile 的可见性哦!
thbcd
thbcd
volatile只能保证变量加载到线程内存时的值是最新,当一个线程抢先修改了变量但是还没执行完的时候,其他线程还是不知道变量被修改的,所以企图拿volatile做同步就是错,老老实实用lock吧
JonasBollack
JonasBollack
亲测,反例必须在Server模式下运行才不会终止,@T-H-E 说的是正解。
断崖逐梦
断崖逐梦
例子有问题吧
JuJu_sh
JuJu_sh
亲测,反例是可以停止线程的
mjchow
mjchow
这个反例的确有点不好重现😅
爱吃大肉包
爱吃大肉包
补充点,重排序不止是编译器的重排序,处理器也会对执行指令进行重排序
JAVA线程9 - volatile关键字

Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),...

小米米儿小
2014/03/04
52
0
C++雾中风景13:volatile解惑

笔者入职百度时,二面面试官的让我聊聊C++之中的volatile关键词。volatile在Java和C++之中的差别可谓是天差地别,我只是简单聊了聊Java之中的volatile,面试官对我的回答并不满意。后续学习《...

LeeHappen
2019/03/19
0
0
关于Java里面多线程同步的一些知识

# 关于Java里面多线程同步的一些知识 对于任何Java开发者来说多线程和同步是一个非常重要的话题。比较好的掌握同步和线程安全相关的知识将使得我们则更加有优势,同时这些知识并不是非常容易...

欧阳海阳
2018/07/13
0
0
Java中volatile关键字的作用

volatile是Java中用来做同步的一个关键字,之前对它的作用一直理解得不是很透彻。 于是在网上查阅了一些资料,发现也讲得含混不清。 后来在wikipedia(http://en.wikipedia.org/wiki/Volatile...

GreenDay
2014/03/08
258
0
java关键字 volatile的作用及使用说明

先来看看这个关键字是什么意思: volatile [ˈvɒlətaɪl] adj. 易变的,不稳定的; 从翻译上来看,volatile表示这个关键字是极易发生改变的。 volatile是java语言中,最轻量级的并发同步机制...

王若伊_恩赐解脱
2018/08/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Batch 小任务(Tasklet)步骤

Chunk-Oriented Processing不是处理 step 的唯一方法。 考虑下面的一个场景,如果你仅仅需要调用一个存储过程,你可以在 ItemReader 中实现这个调用,然后在存储过程完成调用后返回 null。这...

honeymoose
29分钟前
37
0
Linux日志分析

1. Linux日志文件的类型 2. 系统服务日志 2.1 syslogd的简介 2.2 syslogd的配置和使用 2.3 日志的安全性设置 2.4 远程日志记录服务 3. 日志的轮替 3.1 logrotate简介 3.2 logrotate的配置 3....

JiaMing
昨天
50
0
Raspberry Pi 安装系统

下载系统 https://www.raspberrypi.org/downloads/ 使用卡刷工具 启动时开启 ssh 在卡的 boot 根目录创建 ssh 这个空文件,启动后就打开了 ssh 服务 ➜ boot touch ssh ssh 连接 在路由器里...

郭大鹏
昨天
108
0
5、SpringBoot的Profile功能

1.1、功能介绍 配置文件默认使用application.properties、application-default.properties如果需要指定其他配置文件,可以在命令行参数中指定,spring.profiles.default=xxx 激活profilespr...

神锋
昨天
66
0
设计模式-单例

单例模式分为三种:1.饿汉模式,2.懒汉模式,3.登记模式 饿汉模式:在自己被加载时就将自己实例化,即便加载器是静态的 package singleton; public class EagerSingleton { ...

yiduwangkai
昨天
44
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部