
面试官问“不用synchronized怎么实现线程安全的单例”很多人第一反应是懵的线程安全不就是加锁吗不加锁多个线程同时去创建对象怎么会安全其实“不加锁”不等于“不保护”。我们只是把保护工作交给了更底层的机制JVM 的类加载规则、枚举的语法约束、CPU 的原子指令。下面我用一个“宇宙飞船总装车间”的故事把这四种方案串起来。故事背景唯一的核反应堆启动器假设你是一艘宇宙飞船的总工程师。飞船上有一个核心组件核反应堆启动器。整个飞船有成千上万个系统它们在任何时候都只能使用同一个启动器。如果两个线程同时造出两个不同的启动器飞船就会爆炸。总指挥下了死命令为了保证飞船极速运转绝对不允许在车间里挂“闲人免进”的物理大锁。在不挂锁的情况下你怎么保证启动器绝对只有一个这里的“线程安全”指的就是无论多少个线程同时访问系统中永远只存在一个启动器实例。方案一饿汉式——出厂即巅峰最简单粗暴的办法在飞船起飞前就把启动器造好摆在总控室正中央。public class Singleton { private static final Singleton INSTANCE new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }为什么安全因为对象在类加载阶段就已经创建完成。当多线程调用getInstance()时它们只是去“读取”一个已经存在的对象不存在“创建”这个动作。既然没有竞争自然不需要锁。代价哪怕这艘飞船根本不开火这个昂贵的启动器也会一直占着内存。它属于以空间换安全。方案二静态内部类——神秘的黑匣子饿汉式太浪费。于是你造了一个“神秘黑匣子”飞船起飞前不打开只有第一次有人需要启动器时才在里面制造。public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }为什么安全关键在于JVM 的类加载机制。Holder是一个静态内部类它只有在第一次被引用时才会被加载。而 JVM 规范保证一个类的类加载过程是线程安全的只会被执行一次。当多个线程同时调用getInstance()时它们都会尝试触发Holder的加载。JVM 内部会用自己的机制类加载锁不是我们写的业务锁挡住其他线程确保new Singleton()只执行一次。优势懒加载不用不创建线程安全靠 JVM 兜底无锁没有synchronized性能更好。这是实际开发中最推荐的写法之一。方案三枚举式——宇宙法则的烙印如果你想要一个连反射都无法破坏的单例那就动用 Java 的终极武器枚举。public enum Singleton { INSTANCE; public void doSomething() { System.out.println(do something); } }为什么安全Java 语言规范直接规定枚举实例在类加载时创建且绝对只有一个。更重要的是枚举天然免疫反射攻击。即使有人试图通过反射强行创建第二个实例JVM 也会直接抛出异常。《Effective Java》作者 Josh Bloch 说单元素枚举是实现 Singleton 的最佳方式。代价写法相对少见有些团队可能不习惯。但它的安全性是天花板级别的。方案四CAS 无锁——抢座位的量子物理学现在有一个极客工程师不服气为什么一定要等系统或者黑匣子我要靠自己的手速在最极限的情况下把启动器造出来这时候你需要的是CPU 硬件级的原子指令——CASCompare And Swap。public class Singleton { private static final AtomicReferenceSingleton INSTANCE new AtomicReference(); private Singleton() {} public static Singleton getInstance() { Singleton current INSTANCE.get(); if (current null) { Singleton singleton new Singleton(); // 如果 INSTANCE 此时仍然为 null就赋值为 singleton // 如果被别的线程抢先赋值了就什么都不做。 INSTANCE.compareAndSet(null, singleton); current INSTANCE.get(); } return current; } }为什么安全AtomicReference底层调用的是 CPU 的原子指令如 x86 的cmpxchg。当线程 A 和线程 B 同时执行compareAndSet(null, singleton)时CPU 硬件保证只有一个线程能修改成功。另一个线程会发现值已经不是null了修改失败然后直接去拿成功者创建好的对象。这不需要任何synchronized它是靠硬件层面的互斥来保证安全的。注意上面的写法是简化版。实际使用中通常会结合双重检查锁DCL的思想配合volatile来避免重复创建和指令重排序问题。CAS 方案的优势是无阻塞但在高竞争场景下可能产生较多自旋需要根据实际场景权衡。