当前位置: 首页 > 编程笔记 >

谈谈Java中Volatile关键字的理解

庞瀚
2023-03-14
本文向大家介绍谈谈Java中Volatile关键字的理解,包括了谈谈Java中Volatile关键字的理解的使用技巧和注意事项,需要的朋友参考一下

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关键字的知识,希望对大家有所帮助!

 类似资料:
  • 本文向大家介绍谈谈JavaScript的New关键字,包括了谈谈JavaScript的New关键字的使用技巧和注意事项,需要的朋友参考一下 原型和闭包算是JavaScript中最常见,最难以理解,最容易被当做问题的两个部分,当然还有它们的延伸,如作用域链,继承等等吧,我最近也是各种看,各种翻,记录点自己的心得,写写总会让自己的理解更深一些。(跟标题的关系不大啦,就感慨句,每次总感觉自己懂了,再翻还

  • 本文向大家介绍请你谈谈关于Synchronized和lock关键字的理解?相关面试题,主要包含被问及请你谈谈关于Synchronized和lock关键字的理解?时的应答技巧和注意事项,需要的朋友参考一下 考察点:java关键字 synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁、锁粗化、

  • 本文向大家介绍Java中的volatile关键字,包括了Java中的volatile关键字的使用技巧和注意事项,需要的朋友参考一下 volatile修饰符用于让JVM知道访问该变量的线程必须始终将其自身的变量私有副本与内存中的主副本合并。 访问易失性变量将同步所有在主存储器中缓存的变量副本。可变变量只能应用于对象类型或私有类型的实例变量。易失性对象引用可以为null。 示例

  • 本文向大家介绍浅谈Java中static关键字的作用,包括了浅谈Java中static关键字的作用的使用技巧和注意事项,需要的朋友参考一下 static关键字主要有两种作用: 第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。 第二,实现某个方法或属性与类而不是对象关联在一起 具体而言,在Java语言中,static主要有4中使用情况:成员变量、成员方法、代码块和内部类 (1

  • 本文向大家介绍详谈signed 关键字,包括了详谈signed 关键字的使用技巧和注意事项,需要的朋友参考一下 我们都知道且经常用到 unsigned 关键字,但有没有想过,与此对应的 signed 关键字有啥用? 这俩有区别吗?没区别,看起来,signed 完全是个累赘。 真的是这样吗? 我查阅了 C++11 的标准文档(草稿N3690),发现一些端倪: 3.9.1 Fundamental ty

  • 我知道关于volatile有很多问题,但我只是被讨论搞糊涂了:Java:volatile如何保证这段代码中“数据”的可见性? 我读过的每个网站都说可以在缓存中存储一个变量(使这个值对于其他线程不可见),我甚至发现了这个例子https://dzone.com/articles/java-volatile-keyword-0 所以我的第一个问题是:Java是否在缓存中存储变量值(在哪个缓存中?l1 l