单例模式的核心思想
单例模式,说白了就是让一个类在整个程序运行期间,只产生一个实例。就像你们小区的物业办公室,不管有多少住户要报修,最后都是找同一个地方,不会每来一个人就新建一个办公室。在编程里,这种“独一无二”的对象很常见,比如数据库连接池、日志管理器、配置加载器等。
懒汉式:用的时候才创建
这种方式最直白——不到真正需要时,绝不创建实例。适合启动快、使用频率低的场景。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}注意那个 synchronized,它保证了多线程环境下不会出现两个实例。但每次调用 getInstance() 都要加锁,性能有点拖后腿。
双重检查锁定:兼顾效率与安全
为了解决上面的问题,可以改用双重检查。先判断是否为空,再加锁,锁内再判断一次,避免重复创建。
public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}这里 volatile 很关键,防止 JVM 指令重排导致拿到一个还没初始化完成的对象。
饿汉式:一上来就准备好
和懒汉相反,饿汉式在类加载时就把实例建好,天生线程安全,因为 JVM 会保证类初始化的原子性。
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}缺点是不管用不用,都会占用内存。就像你每天上班前都把伞带上,结果一连晴了半个月。
静态内部类:懒加载 + 线程安全
利用 Java 的类加载机制,既实现了延迟加载,又不用手动加锁。
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}
private static class Holder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return Holder.INSTANCE;
}
}只有当调用 getInstance() 时,才会触发内部类的加载,从而创建实例。简洁又高效。
枚举方式:防反射攻击的终极方案
前面几种写法都有可能被反射破坏单例(通过暴力调用构造函数)。而枚举天然防止这一点,还能防止序列化破坏。
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("执行任务");
}
}虽然写法冷门,但在要求绝对安全的场景下,值得推荐。就像保险柜,虽然笨重,但最让人放心。
实际开发中选哪种?如果项目对性能要求不高,直接上静态内部类或枚举;要是想炫技又不怕复杂,双重检查也行。关键是理解背后的原理,而不是死记代码模板。