单例模式----读书笔记

单例模式—读书笔记

单例模式,顾名思义,就是用来创建一个独一无二的,只能有一个实例的对象的入场券。因为在实际应用中,有些对象我们只需要一个,比如说,线程池、缓存、对话框、注册表等。
当然,我们也可以使用全局对象来达到和单例模式一样的作用,但是全局对象有一些缺点,比如说:如果将对象赋值给一个全局变量,那么必须在程序一开始就创建好该对象,如果这个对象很耗资源,但是我们又没有用到它,那就会形成一种浪费。或许我们还可以使用其他方法来达到单例的相同的目的,但是,单例模式很简单,而且经得住时间考验,是值得学习的。
单例模式有很多种实现,下面我们来看一下各种实现的优缺点。

方法1

SingleTon.java

public class SingleTon {
    private static SingleTon singleTon;//私有的,不能在类外部访问,只能在类的内部访问

    private SingleTon(){

    }

    public static SingleTon getInstance(){
        if (singleTon == null){
            singleTon = new SingleTon();
        }
        return singleTon;
    }
}

SingleTon.kt

class SingleTon private constructor(){
    companion object {
        val instance by lazy { SingleTon() }//用到的时候初始化,且只初始化一次
    }
}

总结

单例模式可以保证在任何时刻都只有一个对象(私有构造器)。一般情况下,常常用来管理共享的资源,例如数据库连接和线程池。

方法2:线程安全

上面的java代码在多线程的时候,会出现安全问题。下面我们处理一下多线程的情况:

public class SingleTon{ 
    private static SingleTon singleTon;
    private SingleTon(){
        
    }
    
    private static synchronized SingleTon getInstance(){
        if (singleTon == null){
            singleTon = new SingleTon();
        }
        return singleTon;
    }
}

SingleTon.kt

class SingleTon private constructor(){
    companion object {
        private var INSTANCE: SingleTon? = null

        //在Kotlin中加锁
        @Synchronized
        fun getInstance(): SingleTon {
            if (INSTANCE == null) {
                INSTANCE = SingleTon()
            }
            return INSTANCE!!
        }
    }
}

在上面的方法中,我们在getInstance()方法中,添加了synchoronized关键字,可以保证同时只有一个线程进入该方法,不会有两个线程可以同时进入这方法。

但是,这个方法有一些缺点,只有第一次执行此方法的时候,才需要同步,换句话说,一旦设置了singleTon对象,就不再需要同步这个方法了。不然的话,性能会很低。

改进

1、如果getInstance()方法的性能对应用程序不是很关键,那就什么都别做。
2、使用“饿汉”创建实例,而不是延迟实例化的做法

public class SingleTon{
    private static SingleTon singleTon = new SingleTon();//jvm保证线程安全,且在加载这个类的时候,马上创建此唯一的单例实例。
    private SingleTon(){

    }

    public static SingleTon getInstance(){
        return singleTon;
    }

}

SingleTon.kt

object SingleTon{
   
}

3、用“双重检查加锁”,在getInstance()中减少使用同步

使用双重加锁,首先检查是否实例已经创建了,如果尚未创建,“才”进行同步,这样一来,只有第一次需要同步,这正是我们想要的。

public class SingleTon{
    private volatile static SingleTon singleTon;

    private SingleTon(){

    }

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

volatile 关键字确保,当singleTon变量被初始化成SingleTon实例时,多个线程正确的处理singleTon变量。

SingleTon.kt

class SingleTon private constructor() {
    companion object {
        val singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            SingleTon()
        }
    }
}

方法3:静态内部类

SingleTon.java

public class SingleTon{
    private SingleTon(){

    }

    private static class SingleTonHolder{
        private static SingleTon singleTon = new SingleTon();
    }
    private static SingleTon getInstance(){
        return SingleTonHolder.singleTon;
    }
}

静态内部类的优点是:外部类加载时,不需要立即加载内部类,内部类不加载则不去初始化singleTon,因而不占用内存。只有当getInstance()方法第一次被调用时,才会去初始化singleTon,第一个调用getInstance方法会导致虚拟机加载SingleTonHolder这个类,这种方法不仅能确保线程安全,也能保证单例的唯一性,而且这做到了延迟单例的实例化。

在singleTon创建的过程中,是怎么保证线程安全的呢?如果多个线程同时初始化一个类,那么虚拟机会保证只会有一个线程去初始化这个类,其他线程都需要阻塞等待,直到初始化完毕。因此静态内部类的形式的单例可以保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

SingleTon.kt

class SingleTon private constructor(){
    companion object {//静态方法
        val instance = Holder.holder
    }

    private object Holder {//私有静态内部类
        val holder= SingleTon()
    }
}

版权声明:本文为u011337574原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。