单例模式的几种实现

单例模式的几种实现

在开发中,我们总是会遇到使用单例模式的情况,今天就来总结一下几种实现单例模式的方法。
1.饿汉式

public class SingletonDemo1 {
    //类初始化时,立即加载该对象(没有延时加载的优势)!由于加载类时,天然的线程安全!
    private static SingletonDemo1 instance = new SingletonDemo1();
    //私有构造器
    private SingletonDemo1(){}
    //方法没有同步,调用效率高
    public static SingletonDemo1 getInstance(){
        return instance;
    }
}

不管是否使用到该类,都先初始化该类,有可能会造成资源浪费!
2.懒汉式, 有两种实现方式,
a).方式一

public class Singleton {
    private  static Singleton singleton;
    public Singleton() {
    }

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

这种写法只能在单线程下使用。如果是多线程,可能发生一个线程通过并进入了 if (singleton == null) 判断语句块,但还未来得及创建新的实例时,另一个线程也通过了这个判断语句,两个线程最终都进行了创建,导致多个实例的产生。所以在多线程环境下必须摒弃此方式。
这种写法线程不安全。
b).加入同步锁

public class SingletonDemo2 {
    // 类初始化时,不初始化这个对象(延时加载,懒加载)
    private static SingletonDemo2 instance;
    // 私有构造器
    private SingletonDemo2() {
    }
    // 方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

通过为 getInstence() 方法增加 synchronized 关键字,迫使每个线程在进入这个方法前,要先等候别的线程离开该方法,即不会有两个线程可以同时进入此方法执行 new SingletonDemo2(),从而保证了单例的有效。但它的致命缺陷是效率太低了,每个线程每次执行 getInstance() 方法获取类的实例时,都会进行同步。而事实上实例创建完成后,同步就变为不必要的开销了,这样做在高并发下必然会拖垮性能。所以此方法虽然可行但也不推荐。
网上这样的代码更多,可以很好的工作,但是缺点是效率低。
3. 双重检查锁式。
实际上,早在JDK1.5就引入volatile关键字,所以又有了一种更好的双重校验锁写法。

public class Singleton {
    private volatile static Singleton singleton;
    public Singleton() {
    }

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

DCL体现在进行了两次 if (singleton == null) 的检查,这样既同步代码块保证了线程安全,同时实例化的代码也只会执行一次,实例化后同步操作不会再被执行,从而效率提升很多。
双重检查锁定(DCL)方式也是延迟加载的,它唯一的问题是,由于 Java 编译器允许处理器乱序执行,在 JDK 版本小于 1.5 时会有 DCL 失效的问题。当然,现在大家使用的 JDK 普遍都已超过 1.4,只要在定义单例时加上 1.5 及以上版本具体化了的 volatile 关键字,即可保证执行的顺序,从而使单例起效。
Android 中鼎鼎大名的 Universal Image Loader 和 EventBus 都是采用了这种方式的单例
4).静态内部类式

public class SingletonDemo3 {
    //静态内部类
    private static class SingletonClassGetInstance {
        public static final SingletonDemo3 instance = new SingletonDemo3();
    }
    // 私有构造器
    private SingletonDemo3() {
    }
    public static  SingletonDemo3 getInstance() {
        return SingletonClassGetInstance.instance;
    }
}

这种方式是SingletonDemo3 类被装载了,instance不一定被初始化。因为SingletonClassGetInstance 类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonClassGetInstance 类,从而实例化instance。
5). 枚举式模式

public enum SingletonDemo4 {
    //这个枚举对象本身就是单例对象
    INSTANCE;
    //添加自己需要的操作
    public void operation(){
    }
}

这种模式下,线程安全,调用效率高,但是不能延时加载!实际工作中,少有人使用这种方式!难道是因为太高级了!O(∩_∩)O~
小结
a. 单例模式有五种实现方式
1).饿汉式
线程安全,调用效率高,但是不能延时加载
2).懒汉式
线程安全,调用效率不高,并且可以延时加载
3).双重检查锁式
由于JVM底层内部模型原因,偶尔会出问题,不建议使用
4).静态内部类式
线程安全,调用效率高,并且可以延时加载。推荐使用
5).枚举式
线程安全,调用效率高,不能延时加载,但是可以天然的防止反射和反序列化漏洞
我们在编码中,有关单例模式的具体实现方式,需要根据实际情况来选择!

b. 单例模式中的存在的问题
如果单例类实现了java.io.Serializable接口,那么这个类可能会被反序列化,并且反序列化多次同一对象时,会得到多个单例类的实例。这样就不是单例了!
解决办法,需要在单例类中添加如下方法,


    // 反序列化时,如果定义了readResovle() 则直接返回此方法指定的对象,而不需要单独再创建新对象!
    private Object readResovle() throws ObjectStreamException {
        // TODO Auto-generated method stub
        return instance;
    }

PS:在Android中使用单例模式可能会内存泄露。
当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,那么这个Activity也不会被释放一直到进程退出才会释放。

public class CommUtils {

    private volatile static CommUtils mCommUtils;

    private Context mContext;
    public CommUtils(Context context) {
        mContext=context;
    }

    public static  CommUtils getInstance(Context context) {
        if (mCommUtils == null) {
            synchronized (CommUtils.class) {
                if (mCommUtils == null) {
                    mCommUtils = new CommUtils(context);
                }
            }
        }
        return mCommUtils;
    }
}

解决办法
能使用Application的Context就不要使用Activity的Content,Application的生命周期伴随着整个进程的周期

你可能感兴趣的:(java,设计模式)