☰
Java单例模式: 几种线程安全写法详解
2026/10/15 7:59:46 网站建设 项目流程

单例模式是开发中非常高频的设计模式,保证一个类在整个程序中只有唯一的一个实例对象。比如数据库连接池、线程池、配置管理对象,都适合使用单例。

单例分为饿汉、懒汉两大派系,懒汉模式会遇到多线程并发问题,如果编码不当,会创建多个实例,破坏单例特性。本文将结合多线程知识,梳理几种线程安全的单例写法,分析优缺点。

核心问题:懒汉模式,当多个线程同时调用getInstance(),同时判断instance == null,就会多次 new 对象,线程不安全。

1.饿汉模式

我们来看看代码

  • 类加载的同时,创建实例
public class SingletonHungry { private static SingletonHungry instance=new SingletonHungry(); private SingletonHungry(){} public static SingletonHungry getInstance(){ return instance; } }

2.懒汉模式-单线程版

  • 类加载的时候不创建实例. 第一次使用的时候才创建实例.
public class SingletonLazy { private static SingletonLazy instance=null; private SingletonLazy(){} public static SingletonLazy getInstance(){ if (instance==null){ instance=new SingletonLazy(); } return instance; } }

懒汉模式的优点是:类加载不创建对象, 只有代码真正用到实例的时候才去创建, 节省内存资源

❌ 致命缺点:多线程环境不安全(只能单线程使用)

多线程场景举例: 线程 A 和 线程 B 同时执行if(instance == null)

  1. 线程 A 判断 instance 是 null,刚判断完,还没执行new,CPU 切换到线程 B
  2. 线程 B 也判断 instance 是 null,也准备执行new
  3. 最终两个线程都会创建对象,内存里出现两个不同 Singleton 实例,单例模式失效!

3.懒汉模式-多线程版

那我们可以加上synchronized 来改善上述的多线程环境不安全的问题

public class SingletonLazy { private static SingletonLazy instance=null; private SingletonLazy(){} public synchronized static SingletonLazy getInstance(){ if (instance==null){ instance=new SingletonLazy(); } return instance; } }

4.懒汉模式-多线程版(改进)重点!!!

以下代码是在加锁的基础上做了进一步改动:

  1. 使用双重 if 判定, 降低锁竞争的频率
  2. 给instance 加上了 volatile.
public class SingletonLazy { private static volatile SingletonLazy instance=null; private SingletonLazy(){} public static SingletonLazy getInstance(){ if (instance==null){ //第一层检查 synchronized (SingletonLazy.class){ //加类锁 if (instance==null){ //第二层检查 instance=new SingletonLazy(); } } } return instance; } }

1.两层 if 判断作用

  • 外层if(instance == null):实例已经初始化完成,直接 return,避免每次都进入锁竞争,提升并发性能。
  • 内层if(instance == null):多个线程同时通过外层 if,在 synchronized 处排队,一个线程创建完对象释放锁,下一个拿到锁的线程进来,再次判断,防止重复创建对象。

2.为什么必须加volatile?

instance = new SingletonDCL()这行代码不是一条原子指令,底层分为三步:

  1. 分配对象内存空间
  2. 初始化对象(执行构造方法)
  3. 将 instance 引用指向分配好的内存

JVM 会发生指令重排序。如果发生重排序执行顺序变成:1→3→2。 线程 A 执行到new,先分配内存,引用赋值给 instance,对象还没初始化完成。 此时线程 B 走到外层 if,发现instance != null,直接返回这个半初始化的对象,拿到一个损坏的实例,出现 bug。

volatile关键字两件事:

  • 禁止指令重排序,保证 new 对象三步不会乱序;
  • 保证内存可见性,一个线程修改 instance,其他线程立刻感知最新值。

⚠️没有 volatile,双重检查锁依然线程不安全!

✅优点:懒加载,只有第一次才会竞争锁, 后续调用无锁,并发性能高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询