设计模式--单例模式

2018-10-13  本文已影响0人  吃茶泡饭丶
一点理解

As we know,

单例模式的意图:是为了确保某个类只有一个实例,并能自行实例化提供给外部使用。

细想起来在Android中是怎么保证应用只有一个Application对象的呢?还有InputMethodManager...

五种类型

饿汉式就是自动加载,也就是在调用getInstance时已经产生实例,也就是在类加载时已经进行了实例化,天生的线程安全。
这种模式的缺点就是占用资源,一般情况下我们都希望再用到的时候再去创建实例。
这种方式适合占用资源少,在初始化时就被用到的类中。

Java实现方式:

/**
 * @Author: chichapaofan
 * @CreateDate: 2018/10/13
 * @Description:饿汉单例
 */
public class Singleton_J2 {

    /**
     * 创建私有的实例,防止外部引用
     */
    private static Singleton_J2 singleton = new Singleton_J2();

    /**
     * 私有的构造方法,防止实例化
     */
    private Singleton_J2() {

    }

    /**
     * 提供对外方法,返回实例对象
     * @return
     */
    public static Singleton_J2 getInstance() {
        return singleton;
    }
}

Kotlin实现:

/**
 * @Author: chichapaofam
 * @CreateDate: 2018/10/13
 * @Description:
 */
class Singleton_K2 private constructor() {//私有的主构造器

    /**
     * 被companion object 包围的代码块都是static的
     */
    companion object {
        /**
         * 创建私有的实例,防止外部引用
         */
        private var singleton: Singleton_K2 = Singleton_K2()

        /**
         * 提供对外方法,返回实例对象
         * @return
         */
        fun getInstance(): Singleton_K2? {
            return singleton
        }
    }
}

相比于天生线程安全的饿汉,懒汉有很多的变种!!

懒汉式就是延迟加载,也叫懒加载。在程序需要用到的时候再创建实例,这样保证了内存不会被浪费。
缺点就是线程不安全,但这不是问题!

Java实现方式:

/**
 * @Author: chichapaofan
 * @CreateDate: 2018/10/13
 * @Description: 懒汉单例
 */
public class Singleton_J1 {

    /**
     * 私有的实例防止外部引用
     */
    private static Singleton_J1 singleton = null;

    /**
     * 构造方法私有防止外部实例化
     */
    private Singleton_J1(){

    }

    /**
     * 唯一可以实例化的地方
     * @return
     */
    public static Singleton_J1 getInstance(){
        if(singleton==null){
            singleton=new Singleton_J1();
        }
        return singleton;
    }
}

kotlin实现方式:

/**
 * @Author: chichapaofan
 * @CreateDate: 2018/10/13
 * @Description:
 */
class Singleton_k1 private constructor() {//私有的主构造器
    /**
     * 被companion object包裹的语句都是static的
     */
    companion object {
        /**
         * 创建私有的null实例,防止外部引用
         */
        private var singleton: Singleton_k1? = null

        /**
         * 提供对外方法,返回实例对象
         * @return
         */
        fun getInstance(): Singleton_k1? {
            if (singleton == null) {
                singleton = Singleton_k1()
            }
            return singleton
        }
    }
}

如何做到线程安全?当然是使用Synchronized关键字!

普通变种:

保证线程安全最简单的做法就是将getInstance()方法同步。
同步方法虽然保证了线程安全,但是造成了不必要的同步开销。

Java实现方式:

public class Singleton_J1 {

    /**
     * 私有的实例防止外部引用
     */
    private static Singleton_J1 singleton = null;

    /**
     * 构造方法私有防止外部实例化
     */
    private Singleton_J1(){

    }

    /**
     * 唯一可以实例化的地方
     * @return
     */
    public static synchronized Singleton_J1 getInstance(){
        if(singleton==null){
            singleton=new Singleton_J1();
        }
        return singleton;
    }
}

Kotlin实现方式:

class Singleton_k1 private constructor() {//私有的主构造器
    /**
     * 被companion object包裹的语句都是static的
     */
    companion object {
        private var singleton: Singleton_k1? = null

        @Synchronized fun getInstance(): Singleton_k1? {
            if (singleton == null) {
                singleton = Singleton_k1()
            }
            return singleton
        }
    }
}

高级变种 Double Check Lock

DCL双重检查锁机制(直白的翻译!)
可以很好的解决懒加载单例模式的效率问题和线程安全问题。
缺点:高并发条件下,由于JDK版本问题,在jdk1.5之前会失败。(几率很小)

Java实现方式:

/**
 * @Author: chichapaofan
 * @CreateDate: 2018/10/13
 * @Description:DCL
 */
public class Singleton_J3 {

    private static Singleton_J3 singleton=null;

    private Singleton_J3(){

    }

    public static Singleton_J3 getInstance(){
        /**
         * 判断非空避免造成不必要的同步
         */
        if(singleton==null){
            /**
             * 同步
             */
            synchronized(Singleton_J3.class){
                /**
                 * 判断在singleton为空情况下创建实例
                 */
                if(singleton==null){
                    singleton=new Singleton_J3();
                }
            }
        }
        return singleton;
    }
}

Kotlin实现方式:

class Singleton_k3 private constructor() {

    companion object {

        private var singleton: Singleton_k3? = null

        fun getInstance(): Singleton_k3? {
            /**
             * 判断非空避免造成不必要的同步
             */
            if (singleton == null) {
                /**
                 * 同步
                 */
                synchronized(Singleton_k3) {
                    /**
                     * 判断在singleton为空情况下创建实例
                     */
                    if (singleton == null) {
                        singleton = Singleton_k3()
                    }
                }
            }
            return singleton
        }
    }
}

(饿汉模式的表亲)

和饿汉模式一样,是靠JVM保证类的静态成员只能被加载一次的特点,从JVM层面保证了只会有一个实例对象。
Excuse me?区别在哪?
区别在于静态内部类,类加载时其静态内部类和非静态内部类不会同时被加载。。
(一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生)

Java实现方式:

/**
 * @Author: chichapaofan
 * @CreateDate: 2018/10/13
 * @Description:静态内部类单例
 */
public class Singleton_J4 {

    /**
     * 私有构造方法防止被实例化
     */
    private Singleton_J4() {

    }

    /**
     * 提供外部实例方法
     * @return
     */
    public static Singleton_J4 getInstance() {
        return SingletonHolder.singleton;
    }

    /**
     * 私有的静态内部类
     */
    private static class SingletonHolder {
        private static Singleton_J4 singleton = new Singleton_J4();
    }
}

Kotlin实现方式:

class Singleton_K4 private constructor(){//私有主构造器

    companion object {//static

        fun  getInstance():Singleton_K4{
            return SingletonHolder.singleton
        }

        private class SingletonHolder{
            companion object {
                /**
                 * 在同一模块的任何地方可见
                 */
                internal var singleton:Singleton_K4= Singleton_K4()
            }
        }
    }
}

加载singleton 类时不会初始化instance 只有在调用getInstance 方法时,才会导致instance 被初始化,这个方法不仅能确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。优秀!优秀!!

以上单例实现方法虽然能保证线程安全,但在序列化和反序列化的情况下会出现生成多个对象的情况

怎么解决?Very simple!在单例类中加入readResolve()方法

private Singleton_J3 readResolve(){
        return singleton;
}
/**
 *在静态内部类方式中这样使用
 */
 private Singleton_J4 readResolve(){
        return SingletonHolder.singleton;
 }

当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证。readResolve()的出现允许程序员自行控制通过反序列化得到的对象。

写法简单,线程简单,反序列化也不会重新创建对象,最重要的是默认枚举实例的创建是线程安全的。
But:Android官方的Training课程中明确指出:
Enums ofter require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
枚举通常需要的内存是静态常量的两倍多。你应该严格避免在Android上使用枚举。666

Java实现方式:

/**
 * @Author: chichapanfan
 * @CreateDate: 2018/10/13
 * @Description:枚举单例:声明一个枚举,用于获取数据库连接
 */
public enum Singleton_J5 {
    /**
     *  定义一个枚举的元素,它就是Singleton的一个实例
     */
    INSTANCE;

    private DBConnection connection = null;

    Singleton_J5(){
        connection=new DBConnection();
    }

    public DBConnection doSomething(){
        return connection;
    }
}

如何使用枚举单例:

Singleton_J5 singleton_j5 = Singleton_J5.INSTANCE.doSomething();

需要注意的是单例如果持有Context对象,很容易引起内存泄漏,最好传递全局的Application Context。

上一篇下一篇

猜你喜欢

热点阅读