美文网首页
单例模式

单例模式

作者: uzck | 来源:发表于2017-02-28 17:22 被阅读0次

单例模式在Android中算是很常用的一类模式了,当我们需要整个软件中有且只有一个实例对象时,我们可以写一个单例类。

最简单的单例

public class RecycleBin {
    private static RecycleBin INSTANCE;
    
    private RecycleBin() { }
    
    public static RecycleBin getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new RecycleBin();
        }
        return INSTANCE;
    }

这样的代码大家肯定都不陌生,如果INSTANCE还没有实例化,那么实例化它,并且构造方法是private的,防止客户端new一个实例。这样的代码在单线程环境下可以较好的工作,但是在多线程环境下就会出现错误。假如线程A执行if (INSTANCE == null)这一步后被挂起,线程B切换进来执行了这段代码,实例化了INSTANCE,此时INSTANCE就不为null了,之后把CPU重新让给了线程A,但此时A并不知道INSTANCE已被实例化这件事,又将INSTANCE指向了一个新的RecycleBin对象。

同步的单例模式

那么实现多线程下的单例模式呢。一个很粗暴的答案是:synchronized。是的,给getInstance方法加上这个关键字后,整个方法就是同步的了,可以实现单例模式。

public synchronized static RecycleBin getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new RecycleBin();
        }
        return INSTANCE;
    }

但是这么做会产生新的问题:效率很低。如果有多个线程要同时获得该对象,那么线程需要排队,一个一个获取,这会造成很大的浪费。

改进的单例模式

可以换个角度,因为是在检查INSTANCE == null这一步代码上出现了问题,那么将锁加到这个代码段上即可,将代码修改为

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

DCL(Double Check Locking)

因为synchronized的同步会产生大量的性能开销,追求性能的大佬发明了双重锁的办法来减小开销。

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

比起上面的代码,多了一道检测INSTANCE == null的工序,这行代码可以避免除了第一次以后的同步。当对象已经实例化之后,就不会执行同步代码块了。但是这样的代码还是会存在问题,因为INSTANCE = new RecycleBin()这一行代码不是原子操作
下面来假设一个出错的场景

  1. 线程A执行了getInstance方法
  1. 线程A检查INSTANCE变量,发现为空
  2. 线程A执行INSTANCE == new RecycleBin(),将INSTANCE设置为了非空,但是在构造方法执行前被挂起了
  3. 线程B执行代码,检查INSTANCE变量,发现不为空,返回INSTANCE对象。(但此时INSTANCE还未调用构造方法)

实际上这一行代码被拆成了三步来执行

memory = allocate();            //#1为对象分配内存空间
init(memory);                   //#2初始化
instance = memory;              //#3设置instance,将其指向刚分配的内存空间。

在某些编译器上,2和3会出现倒序,也就是类的域无法得到初始化,从而拿到一个并不正确的对象。
庆幸的是再Java1.5之后的版本可以给INSTANCE加上volitate关键字来避免编译器的优化,拿到正确的对象。

饿汉式

上面的几个方法都是属于懒汉式的方法,即等需要了再去实例化。接下来介绍另一种叫做饿汉式:在类加载时就初始化对象

public class RecycleBin {
    private static RecycleBin INSTANCE = new RecycleBin();
    
    private RecycleBin() { }
    
    public static RecycleBin getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new RecycleBin();
        }
        return INSTANCE;
    }

因为静态域只会加载一次,所以产生的单例对象是安全的。

内部类

public class Singleton {
    // 获得对象实例的方法
    public static Singleton getSingleton() {
        return SingletonHolder.instance;
    }

    /**
     * 静态内部类与外部类的实例没有绑定关系,而且只有被调用时才会
     * 加载,从而实现了延迟加载
     */
    private static class SingletonHolder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton instance = new Singleton();
    }

    private Singleton() {
    }
}

枚举

public enum Singleton {
    // 定义枚举元素,他就是Singleton的一个实例
    INSTANCE;

    public void doSomething() {
        // do something
    }
}

使用枚举可以说是最佳的单例实践方式,因为即便构造器是私有的,仍然可以通过反射来调用私有构造器如

public class TestMain {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Class<?> classType = Singleton.class;  
        Constructor<?> c = classType.getDeclaredConstructor(null);  
        c.setAccessible(true);  
        Singleton singleton1 = (Singleton) c.newInstance();  
        Singleton singleton2 = Singleton.getSingleton();  
        System.out.println(singleton1 == singleton2);  
    }
}

另外还有一种特殊情况是反序列化,反序列化并不是通过调用构造器来构造对象的,反序列化操作提供了readSolve方法来重建对象,如果我们要避免反序列化时产生新的对象需要复写这个方法

private Object readResolve() throws ObjectStreamException {
    return INSTANCE;
}

而枚举帮我们完成了这些工作

小结

这部分也只是看书看了个大概,对多线程环境下单例的各种坑并不了解,这部分同时涉及了Java中类的加载机制,多线程,堆,栈等概念。DCL的错误的那部分也看的不是很懂。。这样一个简单的设计模式都有这么多花样,还要学习一个啊。

参考资料

Android设计模式解析与实战
单例模式各版本的原理与实践
Java线程安全兼谈DCL

相关文章

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

  • 2018-04-08php实战设计模式

    一、单例模式 单例模式是最经典的设计模式之一,到底什么是单例?单例模式适用场景是什么?单例模式如何设计?php中单...

  • 设计模式之单例模式详解

    设计模式之单例模式详解 单例模式写法大全,也许有你不知道的写法 导航 引言 什么是单例? 单例模式作用 单例模式的...

  • Telegram开源项目之单例模式

    NotificationCenter的单例模式 NotificationCenter的单例模式分析 这种单例模式是...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • IOS单例模式的底层原理

    单例介绍 本文源码下载地址 1.什么是单例 说到单例首先要提到单例模式,因为单例模式是单例存在的目的 单例模式是一...

  • 单例

    iOS单例模式iOS之单例模式初探iOS单例详解

  • 单例模式

    单例模式1 单例模式2

  • java的单例模式

    饿汉单例模式 懒汉单例模式

网友评论

      本文标题:单例模式

      本文链接:https://www.haomeiwen.com/subject/igwxgttx.html