专业编程基础技术教程

网站首页 > 基础教程 正文

在Java中,如何实现线程安全的单例模式?

ccvgpt 2025-06-18 19:29:30 基础教程 1 ℃

老实说,双重检查锁在Java界,用的人多,出锅的也多。

为什么要用双重检查?因为我们想偷懒!对,就是偷懒。同步开销大,所以想让同步只在必要的时候触发,也就是“实例还没创建”的时候。但这事儿在早年的Java版本里其实坑不少。

在Java中,如何实现线程安全的单例模式?

先看代码——经典的双重检查写法,大家面试背得都很熟:

public class Singleton {
    privatevolatilestatic Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注意关键点:volatile,这个必须得有。要是没有,那你这套双检锁就是废的。

为啥这么说?因为JVM在new对象的时候,其实做了不少“花活”。你以为一句instance = new Singleton();就是一步操作?太天真了,其实它可以被拆成三步:

  1. 分配内存
  2. 调用构造函数
  3. 将内存地址赋给instance

而JVM可能因为指令重排序,先把引用赋了,再去初始化对象。你说坑不坑?于是某个线程拿到的instance就可能是个“半成品”,这就跟你早上去食堂打饭,阿姨给你一个还没熟的鸡腿,说:“这熟了!”你敢吃?

从JDK 1.5开始,volatile语义被加强,保证了写入操作的“happens-before”关系,这才让这招变得安全可靠。

但是话说回来,这写法我真不推荐在线上系统里用。为什么?太容易出错了,尤其你团队里有实习生的时候……

我一般怎么做呢?直接上静态内部类:

public class Singleton {
    private Singleton() {}

    privatestaticclass Holder {
        privatestaticfinal Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

这方式的优点显而易见:

  • JVM保证线程安全
  • 懒加载(只有在第一次调用getInstance()时才初始化)
  • 没有同步锁的性能开销
  • 写起来还贼清爽,不容易错

你说是不是香?

当然,也不是所有人都喜欢这套,尤其有些人看着内部类就犯密集恐惧症,那你还可以考虑用枚举实现单例:

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 方法逻辑
    }
}

Java里enum天然就是单例的,反序列化和反射攻击都拦得住(除非你有邪门招数),简直像开了挂。但这方式我一般只在“真·单例”的场景下用,比如注册中心、线程池管理器这种,毕竟枚举不太灵活,扩展性差点意思。

那什么时候该用双重检查锁?只在你明确知道这对象构造非常昂贵,且懒加载带来显著收益的场景下。

比如搞个缓存系统,初始化要跑去数据库load一堆配置,这时候懒加载是有意义的。否则就别折腾这套——直接静态初始化或者内部类解决,开发效率和安全性都更高。

现在有些人连写个线程安全单例都喜欢整成Spring Bean……

“反正Spring都能管,你搞个@Component再配个@Scope("singleton")不就完事了?”

这不是不行,而是你把简单问题复杂化了。单例本来JVM自己就能搞定,非得整出IOC来托管,是不是有点吃饱了撑的嫌疑?

所以结论很简单:技术是为业务服务的,别为了“耍帅”搞些炫技式的设计,到头来团队背锅你背大头,这事我见得多了……

Tags:

最近发表
标签列表