双检查锁下的单例懒汉模式

📅 2026/7/5 9:27:44 👁️ 阅读次数 📝 编程学习
双检查锁下的单例懒汉模式

1、什么是单例懒汉模式?

单例模式:保证一个类在JVM中只有一个实例,并提供一个全局访问点。
懒汉模式:在第一次调用获取实例方法时才创建对象,而不是类加载时。核心优点是延迟加载,节省资源;缺点是线程不安全。
举个例子:

publicclassSingleton{privatestaticSingletoninstance1;publicstaticSingletongetInstance1(){if(instance1==null){instance1=newSingleton();}returninstance1;}

对象实例创建流程:

  • 先分配一片足够放下Singleton对象的空间,赋默认值。
  • 再执行构造方法初始化对象。
  • 建立关联,对象引用指向该对象实例。

2、单例懒汉模式下的问题

问题:CPU或者编译器会将第2、3步的指令重排序,单线程下不会有问题。多线程下问题有两个:
T1 如果先执行3,还没执行2的时候,T2 判断得知对象引用不为空,直接返回了未初始化的对象,导致错误。
会导致重复创建,违背单例模式。如果T1在创建对象的过程中,另一个线程T2判断得知instance1==null,那么它会进入对象创建流程,从而创建重复对象并替换引用。
如何解决:

  • 错误方案一:直接给instance1标记为volatile,然后不做任何处理。问题:由于它无法确保原子性,因此创建过程会受其他线程影响,从而导致重复创建。
  • 错误方案二:双重检查锁,问题:拿到未初始化对象。具体见下文:

3、双检查锁失效

首先看一下代码:

publicclassSingleton{privatestaticSingletoninstance2;publicstaticSingletongetInstance2(){if(instance2==null){synchronized(Singleton.class){if(instance2==null){instance2=newSingleton();}}}returninstance2;}}

双检查锁:对象为空时,只让一个线程进去初始化对象。另一个等待线程获取锁后,再次判断是否被初始化,避免重复创建。
为什么能避免重复创建?并发调用getInstance2方法的情况下,后一个线程判断为空之后,还需要等待锁,而那个锁被释放之后。锁内部会再次检查该对象是否被创建,如果已经被创建,则直接跳过对象的创建过程,从而避免再次创建。
问题:只解决了重复创建的问题。还是会出现它被引用,但是没有调用构造方法初始化时。另一个线程通过了非空判断,直接拿到一个未初始化对象的情况。

4、volatile作用

volatile 保证变量的可见性和禁止指令重排序,但不保证原子性。
可见性:写 volatile 会立即刷新到主内存,读 volatile 从主内存读。
重排序:修饰的变量前后插入内存屏障,防止指令重排。
原子性:volatile不能保证i++这类复合操作的原子性,因为读、改、写三步可能被其他线程影响。
因此这里直接利用它的禁止指令重排序的作用,直接修饰单例对象,这样就可以配合synchronized同时保证创建对象过程的原子性、禁止创建过程的指令重排序。

volatileprivatestaticSingletoninstance2;