谈谈Java中Volatile关键字的理解
作者:小眼儿 发布时间:2021-07-08 04:49:13
volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生机。volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。
一、前言
JMM提供了volatile变量定义、final、synchronized块来保证可见性。
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。写了几个测试的例子,大家可以试一试。
二、主程序
public class Main{
public static void main(String[] args) throws InterruptedException{
List<Thread> threadList = new ArrayList<Thread>();
for(int i=0; i<10; ++i){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Single.Holder.instance.add();
}
});
threadList.add(thread);
thread.start();
}
for(Thread thread : threadList)
thread.join();
System.out.println(Single.Holder.instance.x);
}
}
三、单例模式测试
1、没有volatile,没有synchronized的情况
class Single{
public int x = 0;
public void add(){
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
++this.x;
}
public static class Holder{
public static Single instance = new Single();
}
}
输出结果:8, 9, 10都出现过。可以多运行,多试一试,就会发现不同的结果。
2、有volatile,没有synchronized
class Single{
public volatile int x = 0;
public void add(){
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
++this.x;
}
public static class Holder{
public static Single instance = new Single();
}
}
输出结果:最多出现的是9 和 10。
3、没有volatile,有synchronized
class Single{
public int x = 0;
public synchronized void add(){
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
++this.x;
}
public static class Holder{
public static Single instance = new Single();
}
}
输出结果:无论运行多少次都是10。
四、关于volatile在DCL(double check lock)中的应用
public class LazySingleton {
private int someField;
private static LazySingleton instance;
private LazySingleton() {
this.someField = new Random().nextInt(200)+1; // (1)
}
public static LazySingleton getInstance() {
if (instance == null) { // (2)
synchronized(LazySingleton.class) { // (3)
if (instance == null) { // (4)
instance = new LazySingleton(); // (5)
}
}
}
return instance; // (6)
}
public int getSomeField() {
return this.someField; // (7)
}
}
首先说明一下,为什么这种写法在java中是行不通的!
假设线程Ⅰ是初次调用getInstance()方法,紧接着线程Ⅱ也调用了getInstance()方法和getSomeField()方法,我们要说明的是线程Ⅰ的语句(1)并不happen-before线程Ⅱ的语句(7)。线程Ⅱ在执行getInstance()方法的语句(2)时,由于对instance的访问并没有处于同步块中,因此线程Ⅱ可能观察到也可能观察不到线程Ⅰ在语句(5)时对instance的写入,也就是说instance的值可能为空也可能为非空。我们先假设instance的值非空,也就观察到了线程Ⅰ对instance的写入,这时线程Ⅱ就会执行语句(6)直接返回这个instance的值,然后对这个instance调用getSomeField()方法,该方法也是在没有任何同步情况被调用,因此整个线程Ⅱ的操作都是在没有同步的情况下调用 ,这说明线程Ⅰ的语句(1)和线程Ⅱ的语句(7)之间并不存在happen-before关系,这就意味着线程Ⅱ在执行语句(7)完全有可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值,这就是DCL的问题所在。很荒谬,是吧?DCL原本是为了逃避同步,它达到了这个目的,也正是因为如此,它最终受到惩罚,这样的程序存在严重的bug,虽然这种bug被发现的概率绝对比中彩票的概率还要低得多,而且是转瞬即逝,更可怕的是,即使发生了你也不会想到是DCL所引起的。
我的理解是:线程I 和线程II 都有自己的工作存储,线程I 创建好了instance后,向内存刷新的时间是不确定的,所以线程Ⅱ在执行语句(7)完全有可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值。
那么由于在java 5中多增加了一条happen-before规则:
•对volatile字段的写操作happen-before后续的对同一个字段的读操作。
利用这条规则我们可以将instance声明为volatile,即: private volatile static LazySingleton instance;
根据这条规则,我们可以得到,线程Ⅰ的语句(5) -> 语线程Ⅱ的句(2) (也就是线程),根据单线程规则,线程Ⅰ的语句(1) -> 线程Ⅰ的语句(5)和语线程Ⅱ的句(2) -> 语线程Ⅱ的句(7),再根据传递规则就有线程Ⅰ的语句(1) -> 语线程Ⅱ的句(7),这表示线程Ⅱ能够观察到线程Ⅰ在语句(1)时对someFiled的写入值,程序能够得到正确的行为。
补充:在java5之前对final字段的同步语义和其它变量没有什么区别,在java5中,final变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正确运行了。
以上内容是小编给大家介绍的Java中Volatile关键字的知识,希望对大家有所帮助!
猜你喜欢
- 目录前言实践部分测试部分总结前言今天跟小伙伴们分享一个实战内容,使用Spring Boot+Shiro实现一个简单的Http认证。场景是这样
- Bezier曲线的形状是通过一组多边折线(特征多边形)的各顶点唯一地定义出来的。在这组顶点中:(1)只有第一个顶点和最后一个顶点在曲线上;(
- 自定义View一直是横在Android开发者面前的一道坎。一、View和ViewGroup的关系从View和ViewGroup的关系来看,V
- 温馨提示:本教程的 GitHub 地址为「intellij-idea-tutorial」,欢迎感兴趣的童鞋Star、Fork,纠错。首先,给
- 目录切换语言核心代码使用dragonFace改系统语言本篇简单介绍将在Android App中进行语言的切换和使用dragonFace改系统
- 本文为大家分享了Android自定义Toast之WindowManager,供大家参考,具体内容如下Toast:WindowManager三
- 记录窗口上次关闭的位置和大小namespace PDSafe.Base{ public class Se
- 更正说明:我之前的的标题有点文不对题,我这篇博客的内容明明说的是:java中对象创建的过程,对内存之种底层的东西,我其实提的不太多。所以我原
- 使用stream判断两个list元素的属性并输出/*** 使用stream判断两个list中元素不同的item*/@Testpublic v
- 本文介绍了Java中常见死锁与活锁的实例详解,分享给大家,具体如下:顺序死锁:过度加锁,导致由于执行顺序的原因,互相持有对方正在等待的锁资源
- 由于要做一个新项目,所以打算做一个简单的图片验证码。先说说思路吧:在服务端,从一个文件夹里面找出8张图片,再把8张图片合并成一张大图,在8个
- 本文实例为大家分享了使用aop实现全局异常处理的具体代码,供大家参考,具体内容如下日常业务中存在的问题使用大量的try/catch来捕获异常
- 先看代码public class MaxHuiWen {public static void main(String[] args) { &
- idea中创建一个maven项目在pom文件中导入下面的依赖<!--mybatis核心包--> <depend
- 目录方案一,更改启动项方案二,禁用检查应用程序的CAS发布者策略方案一,更改启动项出问题应用的启动项是使用的默认设置,查看App.g.cs文
- itextpdf解决PDF合并的问题本文章是我在项目开发过程中解决了一个关于PDF显示的需求而记录的。需求是这样的,需要将两个PDF进行合并
- 很多时候我们需要在Android设备上下载远程服务器上的图片进行显示,今天整理出两种比较好的方法来实现远程图片的下载。 方法一、直
- 如何从容器中获取对象有时候在项目中,我们会自己创建一些类,类中需要使用到容器中的一些类。方法是新建类并实现ApplicationContex
- 本文介绍了android沉浸式状态栏,分享给大家,希望对大家有帮助一、概述现在主流的App设计风格很多都用到了Materail Design
- 本文实例讲述了java对象转型的概念,分享给大家供大家参考。具体方法如下:对象转型(casting)注意事项如下:1、一个基类的引用类型变量