1、定义
一个类只有一个实例,且该类能自行创建这个实例的一种模式。单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
2、特点
单例模式涉及两个角色: 单例类和访问类。单例类是包含一个实例且能自行创建这个实例的类;访问类就是使用单例的类。单例类只有一个实例对象,该单例对象必须由单例类自行创建,单例类对外提供一个访问该单例的全局访问点。
优点:
1、单例模式可以保证内存里只有一个实例,减少了内存的开销
2、可以避免对资源的多重占用
3、单例模式设置全局访问点,可以优化和共享资源的访问
缺点:
1、单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
2、在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
3、单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则
3、实现方式
下面分别介绍六种实现单例的方式:饿汉式、懒汉式、DCL(Double Check Lock)、静态内部类方式、枚举式和容器实现单例。
1、饿汉式(类加载时创建实例,线程安全)
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,可以直接用于多线程而不会出现问题,是线程安全的。
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
2、懒汉式(延时加载,第一次用到时创建实例,线程安全)
该模式的特点是类加载时没有生成单例,只有当第一次调用 getInstance() 方法时才去创建这个单例。这样一定程度上节约了资源。
如果在多线程环境下使用,则需要使用volatile和synchronized保障线程安全,但是每次调用都要进行同步,消耗资源(此种方式一般不建议使用)
public class LazySingleton {
private static LazySingleton lazySingleton;
private LazySingleton() {
}
public synchronized LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
3、静态内部类实现单例(推荐使用)
第一次加载InnerClassSingleton类时不会初始化instance,只有在第一次调用getInstance()方法时,虚拟机会加载InnerClassSingletonHolder类,初始化instance。
这种方式既保证线程安全,单例对象的唯一,也延迟了单例的初始化,推荐使用这种方式来实现单例模式。
public class InnerClassSingleton {
private InnerClassSingleton() {
}
public static InnerClassSingleton getInstance() {
return InnerClassSingletonHolder.innerClassSingleton;
}
private static class InnerClassSingletonHolder {
private static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
}
}
4、DCL(Double Check Lock)方式实现单例
优点: 资源利用率高,既能够在需要的时候才初始化实例,又能保证线程安全,同时调用getInstance()方法不进行同步锁,效率高。
缺点: 第一次加载时稍慢,由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。
DCL模式是使用最多的单例模式实现方式,除非代码在并发场景比较复杂或者JDK 6以下版本使用,否则,这种方式基本都能满足需求。
public class DclSingleton {
private static DclSingleton instance;
private DclSingleton() {
}
public static DclSingleton getInstace() {
//两层判空,第一层是为了避免不必要的同步;第二层是为了在null的情况下创建实例
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}
5、枚举实现单例(简单)
默认枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例,任何情况下都是一个单例。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("do something");
}
}
6、容器实现单例 (HashMap<key,instance>)
ContainerSingletonManager可以管理多个单例类型,使用时根据key获取对象对应类型的对象。这种方式可以通过统一的接口获取操作,隐藏了具体实现,降低了耦合度。
public class ContainerSingletonManager {
private static Map<String, Object> objectMap = new HashMap<>();
public static void regsiterService(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}