单例模式
饿汉式:
public class HungrySingleton {
//构造函数私有
private HungrySingleton(){
}
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
//对外暴露单例对象接口
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
优点:
实现简单,避免了线程同步问题。
缺点:
当类装载的时候就会创建类实例,不管你用不用,先创建出来,浪费资源
懒汉式
public class LazySingleton {
private static LazySingleton lazySingleton;
private LazySingleton() {
}
//用synchronized修饰同步代码块, 保证多线程下生成唯一的单例
public static synchronized LazySingleton LazySingle() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
优点:
单例只有在使用时,才会被实例化,在一定程度上节约了资源
缺点:
第一次调用时需要及时实例化,反应稍微慢点, 最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销。这种模式一般不建议使用。
懒汉式之DCL模式(double check lock)
public class DCLSingleton {
private static DCLSingleton dclSingleton;
private DCLSingleton(){}
public static DCLSingleton getInstance(){
//第一个null判断是为了判断dclSingle是否为空,避免不必要的同步
if(dclSingleton == null){
synchronized (DCLSingleton.class){
//第二个null判断是为了在null的情况下创建实例
if(dclSingleton == null){
dclSingleton = new DCLSingleton();
}
}
}
return dclSingleton;
}
}
优点:
资源利用率高,第一次执行getInstance时单例对象才会实例化, 效率高。
缺点:
第一次反应稍慢,也由于java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷, 虽然发生概率很小。
DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于1.6版本下使用,否则,这种方式一般能够满足要求。
懒汉式之静态内部类单例模式(IoDH)
public class StaticInnerSingle {
private StaticInnerSingle(){}
public static StaticInnerSingle getInstance(){
return SingletonHolder.staticInnerSingle;
}
//静态内部类
private static class SingletonHolder{
private static final StaticInnerSingle staticInnerSingle = new StaticInnerSingle();
}
/*
* 当第一次加载StaticInnerSingle类时并不会初始化 StaticInnerSingle,只有在第一次调用getInstance方法时
* 才会导致StaticInnerSingle初始化
* 知识点
* */
}
/*
* 当第一次加载StaticInnerSingle类时并不会初始化 StaticInnerSingle,只有在第一次调用getInstance方法时
* 才会导致StaticInnerSingle初始化
* 知识点
* 1.加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生
* */
}
优点:这种方式不仅能够保证线程安全,也能够保证单例对象的唯一性,同时也延迟加载了单例的实例化,所以这是推荐使用的单例模式
缺点:是与编程语言本身的特性相关,很多面向对象语言不支持IoDH
枚举单例
public enum EnumSingleton {
INSTANCE
public void dosomething(){
}
}
优点:
写法简单,线程安全,最大的优点是反序列化时也是单例,其他的单例模式在反序列化不能保证一个单例。
缺点:
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
使用容器模式实现单例模式
public class SingletonManger {
private static Map<String, Object> objectMap = new HashMap<String, Object>();
private SingletonManger(){}
public static void registerService(String key, Object instance){
if(!objectMap.containsKey(key)){
objectMap.put(key, instance);
}
}
public static Object getService(String key){
return objectMap.get(key);
}
}
/**
*在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用隐藏了具体实现,降低了耦合度。
*/
优点
降低了用户的使用成本,也对用隐藏了具体实现,降低了耦合度。
缺点
这种模式在源码中更容易常见, 在Android中,在虚拟机第一次加载comtextImpl类时会注册各种
ServiceFather,将这些服务以键值对的形式存储在一个HashMap中,用户使用时根据key来获取
对应的ServieFetcher,然后通过ServiceFetcher对象的getService函数来获取具体服务对
象。
单例模式的核心
不管以哪种形式实现单例模式,其核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一
实例,在这个获去的过程必须保证线程的安全、防止反序列化导致重新生成实例对象等问题。
选择哪种实现方式取决于项目本身,如是否是复杂的开发环境,JDK版本是否过低、
单例对象的资源等