系统里有个全局配置类,初始化要读文件、连数据库,成本很高,结果 new 得到处都是,内存翻了几倍。这种场景就该上java单例模式,保证一个类在整个 JVM 里只有一个实例。
写单例的要点就两条:构造器私有,不让外部 new;提供一个静态方法拿实例。下面四种写法按场景挑。
饿汉式:简单但加载就建
public class Config { private static final Config INSTANCE = new Config(); private Config() {} public static Config getInstance() { return INSTANCE; } }类加载时就创建,线程天然安全。缺点是不管用不用都占着内存,不过配置类开销一般不大,多数情况这么写完全够。
静态内部类:懒加载又安全
public class Config { private Config() {} private static class Holder { static final Config INSTANCE = new Config(); } public static Config getInstance() { return Holder.INSTANCE; } }第一次调用 getInstance 时才加载 Holder 类,既懒加载又没有锁开销,拿不准就用这个。
双重检查锁:面试常问
private static volatile Config instance; public static Config getInstance() { if (instance == null) { synchronized (Config.class) { if (instance == null) { instance = new Config(); } } } return instance; }两层判空,多数时候不加锁。volatile 不能省,不然指令重排可能让别的线程拿到半初始化的对象,这是面试官最爱抠的点。
枚举和两条注意事项
枚举单例最省心,JVM 保证不重复创建,还能防反射和反序列化破坏。另外记住两点:单例在集群里只对本机有效,几台机器就有几份实例;有状态的单例在多线程下共享数据,动手改之前想清楚线程安全。
实际选择:没并发洁癖就饿汉式,要懒加载用静态内部类,确实需要延迟初始化再加锁的场景才上双重检查,别为了写而写。