Java中的DCL单例详解 volatile

在java中,有很多设计模式

单例模式:
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。


public class Test {
     
 private static final Test INSTANCE = new Test();
 
 private Test() {
     };
 
 public static Test getInstance() {
     
  return INSTANCE;
 }
 
 public static void main(String[] args) {
     
  Test test1 = Test.getInstance();
  Test test2 = Test.getInstance();
 }
}

通过一个INSTANCE 来保存这样一个单例,使得全局只有一个这个类的实例化对象

但是,这种方法有一些问题,当INSTANCE 所代表的类很大,那么对程序来说,如果INSTANCE并没有用到,那么就会造成资源的浪费,所以说就有了第一种优化的方法,当调用 Test.getInstance() 的时候,把这个方法变成这样:


public class Test {
     
 private static Test INSTANCE;
 
 private Test() {
     };
 
 public static Test getInstance() {
     
  if(INSTANCE==null) {
     
   INSTANCE = new Test();
  }
  return INSTANCE;
 }
 
 public static void main(String[] args) {
     
  Test test1 = Test.getInstance();
  Test test2 = Test.getInstance();
 }
}

此时,出现了另一个问题,当同时调用很多次这个getInstance方法,并发情况很大,如果第一个线程访问到了判断INSTANCEnull时候,第二个线程同样也进入了程序,并且在第一个线程并没有进行 INSTANCE = new Test();的时候,也判断了INSTANCEnull,那么,就会有两个INSTANCE 被初始化

此时,我们可以使用synchronized 来进行加锁,让只有一个线程来进行访问,不允许并发,那么代码就修改为这样:


public class Test {
     
 private static Test INSTANCE;
 
 private Test() {
     };
 
 public static Test getInstance() {
     
  if(INSTANCE==null) {
     
   synchronized(Test.class) {
     
    if(INSTANCE==null) {
     
     INSTANCE = new Test();
    }
   }
  }
  return INSTANCE;
 }
 
 public static void main(String[] args) {
     
  Test test1 = Test.getInstance();
  Test test2 = Test.getInstance();
 }
}

我们注意到,使用了两次判断 if(INSTANCE==null) ,主要原因是因为避免当第一个程序进行了访问,进行加锁的时候,第二个程序也进行访问,等待解锁,当第一个程序进行初始化Test后,第二个程序解锁又一次进入这个方法进行了一个初始化。所以加入了两次if判断,也就是:DCL(double check lock)

volatile有两个作用,保持线程可见性,禁止指令重排序,需要注意的是,当我们使用DCL时候,要定义INSTANCE为volatile,

private static volatile Test INSTANCE;

原因:
new 过程为:

  1. 申请内存空间(半初始化)
  2. 调用构造方法
  3. 建立关联(Date d= new Date() d与new Date()建立联系)

当没有加volatile ,有时候会进行指令重排序,如果调用构造方法和建立关联如果发生指令重排序,那么就会先建立关联,在调用构造方法。表面上可能没有问题,但是,如果在建立关联,调用构造方法之前(构造方法没有执行),第二个线程进行访问,此时INSTANCE不为空,但由于没有调用构造方法,成员变量没有赋值,则就会出现隐患。

—学习于b站 马士兵说 的美团面试七连问

你可能感兴趣的:(Java小知识点,java,设计模式,编程语言,多线程)