单例模式

62 阅读5分钟

单例模式的意图

确保某个类只有一个实例,并能自行实例化提供给外部使用。

基本要求

  • 构造器私有化,private修饰,防止外部私自创建该单例类的对象实例;
  • 提供一个该实例对象全局访问点;
  • 在多线程环境下保证单例类有且只有一个对象实例,以及在多线程环境下获取单例类对象实例需要保证线程安全。
  • 在反序列化时保证单例类有且只有一个对象实例。

单例模式的几种实现

  • 饿汉式

饿汉式:通过类加载的方式进行实例化,线程安全。

Java实现方式:

public class Singleton_J2 implements Serializable {

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

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

    }

    /**
     * 提供对外方法,返回实例对象
     * @return
     */
    public static Singleton_J2 getInstance() {
        return singleton;
    }
    
    //防止单例对象在反序列化时重新生成对象
    private Object readResolve() throws ObjectStreamException {
        return mInstance;
    }
}

Kotlin实现:

object KotlinSingleton :Serializable {

    fun doSomething(){
        
    }

    //防止单例对象在反序列化时重新生成对象
    private fun readResolve():Any{
        //反序列化时会调用readResolve这个钩子方法,只需要把当前的KotlinSingleton对象返回而不是去创建一个新的对象
        return KotlinSingleton
    }
    
}
//在Kotlin中使用KotlinSingleton
fun main(args: Array<String>) {
    //调用单例类中的方法
    KotlinSingleton.doSomething()
}

//在Java中使用KotlinSingleton
public class Test {
    public static void main(String[] args) {
        //通过拿到KotlinSingleton的静态实例INSTANCE,通过INSTANCE调用单例类中的方法
        KotlinSingleton.INSTANCE.doSomething();
    }
}
  • 懒汉式

在第一次调用getInstance()方法时创建实例,这样保证了内存不会被浪费,线程不安全。

Java实现方式:

public class Singleton_J1 implement Serializable {

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

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

    }

    /**
     * 懒汉式是非线程安全,需要使用synchronized将getInstance()方法同步,来保证线程安全
     * @return
     */
    public static synchronized Singleton_J1 getInstance(){
        if(singleton==null){
            singleton=new Singleton_J1();
        }
        return singleton;
    }
    
    //防止单例对象在反序列化时重新生成对象
    private Object readResolve() throws ObjectStreamException {
        ...
    }
}

kotlin实现方式:

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

        /**
         * 提供对外方法,返回实例对象
         * @return
         */
        @Synchronized
        fun getInstance(): Singleton_k1? {
            if (singleton == null) {
                singleton = Singleton_k1()
            }
            return singleton
        }
    }
    
    //防止单例对象在反序列化时重新生成对象
    private fun readResolve():Any{
        ...
    }
}
  • 双重检查锁机制(DCL)

解决了懒加载方式的效率和线程安全问题。

Java实现方式:

public class Singleton_J3 implements Serializable{

    private volatile static Singleton_J3 singleton;

    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;
    }
    
    private Object readResolve() throws ObjectStreamException {
        return mInstance;
    }
}

Kotlin实现方式:

class KotlinSingleton private constructor() : Serializable {

    fun doSomething() {
        
    }

    companion object {
        @JvmStatic
        //使用lazy属性代理,并指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全
        val instance: KotlinSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            KotlinSingleton()
        }
    }

    //防止单例对象在反序列化时重新生成对象
    private fun readResolve(): Any {
        //反序列化时会调用readResolve这个钩子方法,只需要把当前的KotlinSingleton对象返回而不是去创建一个新的对象
        return KotlinSingleton
    }

}
为什么使用volatile修饰singleton?

volatile 可以禁止指令重排

在调用new Singleton_J3(); 时伪代码如下:

memory=allocate(); //1:分配内存空间
ctorInstance();   //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间

说明:再多线程情况下,当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,并且被JVM允许。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getInstance()方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。

  • 静态内部类

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

Java实现方式:

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

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

    }

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

    /**
     * 私有的静态内部类
     */
    private static class SingletonHolder {
        private static final Singleton_J4 instance = new Singleton_J4();
    }
    
     //防止反序列化重新创建对象
    private Object readResolve() {
        return SingletonHolder.sInstance;
    }
}

Kotlin实现方式:

class KotlinSingleton private constructor() : Serializable {

    fun doSomething() {

    }

    companion object {
        fun getInstance(): KotlinSingleton {
            return SingletonHolder.instance
        }
    }

    private fun readResolve(): Any {
        return SingletonHolder.instance
    }

    private object SingletonHolder {
        val instance: KotlinSingleton = KotlinSingleton()
    }
}

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

当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证。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实现方式:

public enum JavaSingleton {
        INSTANCE;

        public void doSomeing(){
            
        }

}

Kotlin实现方式:

enum class KotlinSingleton {
    INSTANCE;

    fun doSomeing() {
        
    }

}

如何使用枚举单例:

Singleton_J5 singleton_j5 = Singleton_J5.INSTANCE.doSomething();

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