Java 单例模式

单例模式是开发中较为常见也较为简单的一种设计模式,单例模式的实现有多种,每种都有自己的特点,在这里整理一下,从而可以从一定高度来认识单例模式并应用单例模式。

单例模式

  1. 私有的构造方法
  2. 通过一个公有静态方法或枚举返回单例对象
  3. 确保多线程时单例对象有且只有一个
  4. 确保单例对象反序列化时不会重新构造对象

一、饿汉模式

缺点:在类加载时对象就生成,不管是否使用,类卸载时对象才被销毁
优点:不存在线程同步问题,避免使用 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 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);
    }
}

你可能感兴趣的:(Java 单例模式)