单例模式是开发中较为常见也较为简单的一种设计模式,单例模式的实现有多种,每种都有自己的特点,在这里整理一下,从而可以从一定高度来认识单例模式并应用单例模式。
单例模式
- 私有的构造方法
- 通过一个公有静态方法或枚举返回单例对象
- 确保多线程时单例对象有且只有一个
- 确保单例对象反序列化时不会重新构造对象
一、饿汉模式
缺点:在类加载时对象就生成,不管是否使用,类卸载时对象才被销毁
优点:不存在线程同步问题,避免使用 synchronized 造成的性能问题
// 饿汉模式 在类被加载时初始化单例对象,稍微有点消耗资源
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstanse() {
return singleton;
}
}
二,懒汉模式
// 懒汉模式 在用户第一次调用时初始化单例对象,缺点,每次获取时都会同步判断,浪费资源
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstanse() {
if(singleton == null){
singletor = new Singleton();
}
return singleton;
}
}
三、双重判断 Double Check Lock DCL
JDK 1.5 及之前可能发生双重锁失效问题,因为 sigleton = new Singleton() 并不是原子性操作,由于 Java 支持编译乱序,则可能发生如果 A 线程中先为 singleton 赋值为对象的内存地址而真实对象还未实例化,此时 B 线程过来由于 singleton 不为 null 所以 B 线程不会被 synchronized 影响,此时 B 直接得到 singleton ,但由于使用时 singleton 还未实例化结束,所以会发生问题。1.6 及以后的版本可以使用 volatile 关键字,使用 volatile 关键字修饰 singleton ,会保证 singleton 初始化成功后再赋值。
public class Singleton {
private Singleton() {
}
private volatile static Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null)
singleton = new Singleton();
}
}
return sigleton;
}
}
四、静态内部类
简单方便,利用 Java 类加载机制保障线程安全,是推荐的方式
类加载时不会加载内部类
public class Singleton {
private Singleton() {
}
private static class InnerClass {
public static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return InnerClass.singleton;
}
}
解决反序列化时新建单例的问题
在类中重写 readResolve 方法,并在其中返回单例对象
private Object readResolve() throws ObjectStreamException(){
return singleton; // 如果不重写,默认是重新生成一个新的对象
}
五、枚举
简单逼格高,开销大
枚举在 Java 中与普通的类是一样的,可以有字段,还能有自己的方法。最重要的是枚举实例的创建是线程安全的,且在任何情况下它都是一个实例,即使反序列化时也是一个实例
public enum Singleton {
INSTANCE;
// 枚举中也可以跟正常类一样定义方法
public void doSomethind(){}
}
六、使用容器实现单例模式
初始化时将多种单例类型注入到一个统一的管理类中,在使用时根据 key 获取对应类型的对象。降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。
public class SingletonManager {
private static final Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void putObj(String key, Object value) {
if (objMap.containsKey(key)) return;
objMap.put(key, value);
}
public static Object getObj(String key) {
return objMap.get(key);
}
}