单例核心:保证一个类在整个 JVM 中只有一个实例对象,提供全局访问点。
1. 饿汉式(静态常量,线程安全,简单常用)
类加载直接实例化,没有懒加载,类初始化就创建对象。
/**
* 饿汉式单例
* 优点:实现简单,天然线程安全
* 缺点:类加载就创建实例,没有懒加载,如果对象永远不用,造成内存浪费
*/
public class SingletonHungry {
// 类加载直接初始化实例
private static final SingletonHungry INSTANCE = new SingletonHungry();
// 私有构造器,禁止外部new
private SingletonHungry() {}
// 对外获取实例方法
public static SingletonHungry getInstance() {
return INSTANCE;
}
}
2. 懒汉式(非线程安全,禁止生产使用)
需要的时候才创建实例,懒加载,多线程会创建多个对象。
/**
* 懒汉式,线程不安全,多线程环境会失效,仅做演示,不要上生产
*/
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
3. 双重检查锁 DCL(Double‑Checked Locking,生产最常用)
线程安全、懒加载、性能好,业务开发首选,必须加 volatile 禁止指令重排。
/**
* DCL双重检查锁单例模式【推荐业务使用】
* volatile:防止new对象时指令重排导致半初始化对象被返回
*/
public class SingletonDCL {
// volatile非常关键
private static volatile SingletonDCL instance;
private SingletonDCL() {
// 防止反射暴力破解单例(可选增加防御)
if(instance != null){
throw new RuntimeException("禁止反射创建实例");
}
}
public static SingletonDCL getInstance() {
// 第一次判断,避免每次竞争锁
if (instance == null) {
synchronized (SingletonDCL.class) {
// 第二次判断,防止多线程等待锁后重复创建
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
new SingletonDCL()分三步:分配内存 → 初始化对象 → 赋值引用;不加 volatile,JVM 可能重排顺序,返回未初始化完成的对象。
4. 静态内部类单例(优雅,线程安全,懒加载)
利用类加载机制,外部类加载不会加载内部类,调用 getInstance 才加载,推荐。
/**
* 静态内部类单例
* 优点:懒加载、线程安全、无锁,代码简洁
* 缺点:反射依然可以破坏单例
*/
public class SingletonInner {
private SingletonInner(){}
// 静态内部类,只有调用才会加载
private static class SingletonHolder{
private static final SingletonInner INSTANCE = new SingletonInner();
}
public static SingletonInner getInstance(){
return SingletonHolder.INSTANCE;
}
}
5. 枚举单例(Effective Java 推荐,防止反射、序列化破坏)
最安全,杜绝反射、序列化漏洞,缺点不是懒加载
/**
* 枚举实现单例,《Effective Java》推荐
* 天然防止反射、序列化破坏单例
*/
public enum SingletonEnum {
INSTANCE;
// 业务方法写在这里
public void doSomething(){
System.out.println("枚举单例执行业务逻辑");
}
}
使用示例
public class TestMain {
public static void main(String[] args) {
SingletonDCL s1 = SingletonDCL.getInstance();
SingletonDCL s2 = SingletonDCL.getInstance();
System.out.println(s1 == s2); // true
SingletonEnum.INSTANCE.doSomething();
}
}
各方案对比总结
注意:DCL、静态内部类都可以被反射暴力调用私有构造器破坏单例;枚举是 Java 底层语法层面规避该问题。
本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。
原文链接
欢迎访问