欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

DCL(双检锁单例模式)到底需不需要volatile?

程序员文章站 2022-10-04 08:21:13
DCL(双检锁单例模式)到底需不需要volatile?先给答案,确定以及肯定的告诉你,需要使用 volatile。我们先看代码,然后一步一步分析:package cn.frantic.learning.jvm;public class T08_DCL {public volatile static T08_DCL instance;public static T08_DCL getInstance() {if(instance == null) {synch...

先给答案,确定以及肯定的告诉你,需要使用 volatile。我们先给出正确的 DCL 单例模式,然后一步一步分析:

package cn.frantic.learning.jvm;

public class T08_DCL {

	public volatile static T08_DCL instance;
	
	public static T08_DCL getInstance() {
		
		if(instance == null) {
			
			synchronized (T08_DCL.class) {
				
				if(instance == null) {
					
					instance = new T08_DCL();
				}
				
			}
		}
		return instance;
	}
}

volatile 的作用是什么?

  1. 保证变量的可见性:当一个线程修改一个被 volatile 修饰的变量的时候,其他的线程可以立刻等到修改后的值。
  2. 屏蔽指令重排序:指令重排序是编译期和处理器为了高效而对程序的优化手段,他保证(单线程)程序的执行结果不变,但是不保证程序的执行顺序和代码的顺序一致,这里可以参考 CPU 为什么乱序执行(指令重排序)

为什么要用 volatile?

解答之前,我们先看一个简单实例,观察一下 new 一个对象对应的汇编指令,Java 代码如下:

package cn.frantic.learning.jvm;

public class T09_NewObject {
	
	public void test() {
		System.out.println("11");
	}
	
	public static void main(String[] args) {
		
		T09_NewObject t09_NewObject = new T09_NewObject();
		t09_NewObject.test();
	}

}

先编译成字节码,javac TT09_NewObject
然后查看汇编命令,javap -c T09_NewObject.class,结果如下:

Compiled from "T09_NewObject.java"
public class cn.frantic.learning.jvm.T09_NewObject {
  public cn.frantic.learning.jvm.T09_NewObject();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void test();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String 11
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #5                  // 创建对象,并将对象引用压入栈
       3: dup								// 栈内复制一份对象的引用并压入栈,此时栈内有两份引用
       4: invokespecial #6                  // pop 弹出一个引用,并调用对象的构造函数,完成对象的初始化
       7: astore_1							// pop 弹出一个引用,并将引用赋值给变量 t09_NewObject
       8: aload_1
       9: invokevirtual #7                  // Method test:()V
      12: return
}

从汇编指令中可以看出对象的创建大致分为三部分:

  • 创建一个空白对象,也就是分配一块内存
  • 调用对象的构造方法完成初始化操作
  • 将对象的引用赋值给变量

了解指令重排序的同学,应该就能明白为什么要使用 volatile 了。如果是在单线程的情况下,指令重排序不会印象创建对象最终的结果。但是如果在多线程的情况下,指令重排序很有可能让其他线程拿到的是一个指向空白对象的应用。

本文地址:https://blog.csdn.net/qq_35918357/article/details/107350803