单例模式学习笔记

单例模式确保某一个类有且只有一个实例,并且自行实例化向整个系统提供这个实例,当某种类型的对象只应该有一个。比如,访问数据库和IO等资源的时候就可以考虑使用单例模式。

实现单例模式有以下几个注意点:
1 构造方法私有化;
2 通过一个静态方法或者枚举返回单例类对象;
3 确保在多线程情况下单例类的对象只有一个;
4 确保单例类对象在反序列化时不会重新构建对象;

通过构造方法私有化使得不能通过new关键字来构建该单例类的实例,客户端通过调用该单例类暴漏的静态方法来获取单例类的唯一对象,同时需要确保获取这个单例类过程中的线程安全,即在多线程的情况下单例类的对象有且只有一个。

饿汉模式

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return singleton;
    }
}

懒汉模式

public class Singleton {
    private static Singleton singleton;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

在 getInstance 方法中使用synchronized关键字的作用是为了确保在任何时刻只有一个线程执行getInstance方法中的代码。懒汉模式实现单例只有在使用的时候才会实例化,一定程度上节约了资源,但第一次加载时需要及时进行实例化反应较慢,并且每次调用getInstance方法都需要进行同步,造成了不必要的同步开销。

双重检查锁定(DCL)实现单例

public class Singleton {
    private volatile static Singleton singleton = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

DCL方式实现单例既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance方法不在进行同步锁。在getInstance方法中对singleton进行了两次判空:第一层判断是为了不必要的同步,第二层是为了在null的情况下创建实例,通过volatile关键字来防止指令重排序。

静态内部类单例模式

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.SINGLETON;
    }

    private static class SingletonHolder {
        private static final Singleton SINGLETON = new Singleton();
    }
}

第一次加载Singleton这个类时并不会初始化SINGLETON,只有在第一次调用getInstance方法时才会导致SINGLETON初始化,第一次调用getInstance方法时会导致虚拟机加载SingletonHolder类,这种方式不仅确保单例对象的唯一性,同时也延时了单例的实例化,因此推荐使用静态内部类的方式实现单例。

枚举单例

public enum SingletonEnum {

    INSTANCE;

    public void doSomething() {
        System.out.println("this is sigleton");
    }
}

默认枚举实例的创建是线程安全的,并且在任何情况下都是一个单例。需要注意的是无论是懒汉、饿汉是还是双重检查锁定、静态内部类实现单例在反序列化的情况下都会出现重新创建对象,那么该如何避免的?我们仅需要加入readResolve函数即可。

public final class Singleton implements Serializable {

    private static final long serialVersionUID = 0L;
    private static final Singleton SINGLETON = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SINGLETON;
    }

    private Object readResolve() throws ObjectStreamException {
        return SINGLETON;
    }

}

注意:
1 可序列化类中字段类型不是java的内置类型,那么该字段类型也需要实现 Serializable 接口
2 如果调整了可序列化类的内部结构,比如新增,去除某个字段,但没有修改 serialVersionUID,那么会引发 java.io.InvalidClassException 异常或者导致某个属性为0或者null,最好的解决方案是将serialVersionUID设置为0L,这样即使修改了类的内部结构也不会导致java.io.InvalidClassException异常,而只是将新增的字段设置为0或者null

使用容器实现单例模式

public class SingletonManger {
    private static Map<String, Object> singleMap = new HashMap<>();

    private SingletonManger() {
    }

    public static void registerService(String key, Object value) {
        if (!singleMap.containsKey(key)) {
            singleMap.put(key, value);

        }
    }

    public static Object getService(String key) {
        return singleMap.get(key);
    }
}

通过一个管理类来统一管理多种单例类型,在需要时根据 key 来获取对应类型的对像。

参考资料:

《Android源码设计模式解析与实战》

猜你喜欢

转载自blog.csdn.net/ku_tengfei/article/details/79274775