单例设计模式

作用:

对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是非常可观的一笔系统开销。由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
所以对于系统的关键组件和被频繁操作的对象,使用单例模式便可以有效地改善系统性能。

下面来说说单例模式的几种写法

(1)饿汉式单例模式
/**
 * 饿汉式(Java)
 */

public class SingletonDemo {

    private static SingletonDemo singletonDemo = new SingletonDemo();

    private SingletonDemo(){}

    public static SingletonDemo getInstance(){
        return singletonDemo;
    }
}

/**
 * 饿汉式(Kotlin)
 */
object SingletonDemo {
    val instance: SingletonDemo = SingletonDemo
}

以上是饿汉式单例的标准写法,分析结果如下:

  • 由于SingletonDemo实例的创建时静态的,所以当SingletonDemo加载的时候就会创建实例;
  • 当SingletonDemo中的getInstance方法被执行的时候,SingletonDemo将被加载,从而new了一个SingletonDemo实例;
  • 但是当SingletonDemo类中有其他成员的时候,假如多了一个A方法,首先执行A方法后,SingletonDemo实例也会被创建,也就是说,在不需要SingletonDemo对象的时候依然创建了SingletonDemo实例,造成资源不必要的浪费,那么有没有办法做到当使用的时候再创建实例呢?

想要避免以上弊端就不能让SingletonDemo静态加载,我们改写代码:

/**
 * 懒汉式(java)
 */
public class SingletonDemo {

    private static SingletonDemo instance;

    private SingletonDemo(){}

    public static SingletonDemo getInstance(){
        if(instance == null){
            instance = new SingletonDemo();
        }
        return instance;
    }
}

/**
 * 懒汉式(Kotlin)
 */
object SingletonDemo {
    var instance: SingletonDemo? = null
        get() {
            if (field == null) {
                field = SingletonDemo
            }
            return field
        }
        private set
}

采用延迟加载(懒汉式)的方式解决以上弊端,但是这样解决方式并不是完美的,我们不仅要考虑实例单一,还要考虑线程安全,很显然以上代码会带来线程安全危机,当多个线程同时调用getInstance方法时,有可能会new多个实例,接下来我们来解决线程安全问题。

(2)懒汉式单例模式
/**
 * 线程安全的懒汉式(Java)
 */

public class SingletonDemo {

    private static SingletonDemo instance;

    private SingletonDemo(){}

    public static synchronized SingletonDemo getInstance(){
        if(instance == null){
            instance = new SingletonDemo();
        }
        return instance;
    }
}

/**
 * 线程安全的懒汉式(Kotlin)
 */
object SingletonDemo {
    @get:Synchronized
    var instance: SingletonDemo? = null
        get() {
            if (field == null) {
                field = SingletonDemo
            }
            return field
        }
        private set
}

以上是线程安全的懒汉式的标准写法。

添加synchronized锁,使在同一时间内,只能有一个线程执行getInstance方法,这样虽然决绝了线程安全的问题,但是效率严重降低,那么怎么才能提高效率呢?

(3)双检查锁机制(DCL:double checked locking)
/**
 * 双重校验锁(Java)
 */
public class SingletonDemo {

    private static SingletonDemo instance;

    private SingletonDemo(){}

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

/**
 * 双重校验锁(Kotlin)
 */
object SingletonDemo {
    var instance: SingletonDemo? = null
        get() {
            if (instance == null) {
                synchronized(SingletonDemo::class.java) {
                    if (instance == null) {
                        instance = SingletonDemo
                    }
                }
            }
            return instance
        }
        private set
}

这里有两处判空的地方,如果第一次判断的时候不为空,则跳过synchronized,使效率提高。

这个方案当时被认为是单例模式的最佳方案,但是由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况,jdk1.6之后,只要定义为private volatile static SingleTon instance 就可解决DCL失效问题, volatile确保instance每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅,volatile可以保证即使java虚拟机对代码执行了指令重排序,也会保证它的正确性。(volatile:防止编译器对代码进行优化)

/**
 * 双重校验锁(Java)
 */
public class SingletonDemo {

    private volatile static SingletonDemo instance;

    private SingletonDemo(){}

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

/**
 * 双重校验锁(Kotlin)
 */
object SingletonDemo {
    @Volatile
    var instance: SingletonDemo? = null
        get() {
            if (instance == null) {
                synchronized(SingletonDemo::class.java) {
                    if (instance == null) {
                        instance = SingletonDemo
                    }
                }
            }
            return field
        }
        private set
}

以上代码才是DCL的标准写法,一定要添加volatile修饰才能保证绝对安全性。

所谓 指令重排 的概念可以举例说明:

创建一个对象需要3条指令:
    指令1:开辟内存
    指令2:对象初始化(内存填值)
    指令3:将指针指向该内存

volatile 是一个指令关键字。
如果不添加 volatile ,那么在多线程的情况下,99.99%的概率是线程安全的,但是有0.01%的概率是不安全的。
如果一旦触发这个0.01%的概率,发生了`指令重排`,那么创建对象的指令可能变成:

    指令1:开辟内存
    指令3:将指针指向该内存 ---- 此时对象已经不为空了
    指令2:对象初始化(内存填值)

这样会导致对象没有初始化完成。
(4)静态内部类单例模式
/**
 * 静态内部类(Java)
 */

public class SingletonDemo {

    private SingletonDemo(){}

    static class SingleHolder{
        public static SingletonDemo instance = new SingletonDemo();
    }

    public static SingletonDemo getInstance(){
        return SingleHolder.instance;
    }
}

/**
 * 静态内部类(Kotlin)
 */
object SingletonDemo {
    val instance: SingletonDemo
        get() = SingleHolder.instance

    internal object SingleHolder {
        var instance: SingletonDemo = SingletonDemo
    }
}

或者

/**
 * 静态内部类(Kotlin)
 */
class SingletonDemo {
    companion object {
        val instance: CrashHandler
            get() = CrashHandlerHolder.instance

        internal object CrashHandlerHolder {
            var instance: CrashHandler = CrashHandler()
        }
    }
}

分析一下这种写法:
(1)当调用SingletonDemo类中的其他成员时,SingletonDemo加载之后并没有创建实例,所以解决了饿汉式的弊端;(只有SingleHolder内部类被加载的时候才会创建SingletonDemo实例)
(2)由于静态的特性,没有线程安全问题,所以解决了懒汉式的弊端;
(3)由于反射机制无法侵入静态内部类,从而加深了安全性;

(5)用枚举实现单例模式

其实,前四种设计模式存在一个共同的弊端,那就是一旦序列化之后就不再单例,具体原因可以查询其他资料。

public class SingletonDemo {

    private String A;

    private String B;

    public enum  MyEnumSingleton {

        INSTANCE;

        private SingletonDemo singletonDemo;

        private MyEnumSingleton(){
            singletonDemo = new SingletonDemo();
        }

        public SingletonDemo getInstance(){
            return singletonDemo;
        }
    }

    public static SingletonDemo getInstance(){
        return MyEnumSingleton.INSTANCE.getInstance();
    }
}

以上代码就是枚举单例了。

    SingletonDemo singletonDemo1 = SingletonDemo.getInstance();
    System.out.println(String.valueOf(singletonDemo1.hashCode()));
    SingletonDemo singletonDemo2 = SingletonDemo.getInstance();
    System.out.println(String.valueOf(singletonDemo2.hashCode()));
    SingletonDemo singletonDemo3 = SingletonDemo.getInstance();
    System.out.println(String.valueOf(singletonDemo3.hashCode()));

打印hashcode发现是一致的,说明singletonDemo1 、singletonDemo2 、singletonDemo3 是同一个对象。

枚举单例的优点:
  • 线程安全
  • 代码简洁
  • 序列化之后依然是单例
  • 反射机制无法侵入
枚举单例的缺点:
  • 消耗的内存大概是静态内部类单例的两倍
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,755评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,305评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,138评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,791评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,794评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,631评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,362评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,264评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,724评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,040评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,742评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,364评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,944评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,060评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,247评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,979评论 2 355

推荐阅读更多精彩内容

  • 什么是设计模式? 是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。一些开发的套路,用于解决某一些特...
    Spring618阅读 660评论 0 0
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,254评论 4 34
  • 1.应用场景: 当需要保证类在内存中的对象唯一性,可以使用单例模式,不想创建多个实例浪费资源,或者避免多个实例由于...
    发光的鱼阅读 267评论 0 0
  • 设计模式在软件开发人员中非常流行。设计模式是一种通用软件问题的精妙解决方案。单例模式是Java创建型设计模式中的一...
    唐先僧阅读 897评论 2 21
  • 单例设计模式(Singleton Pattern)是Java开发人员了解设计模式的第一种,也是最容易理解的,在平时...
    Michaelhbjian阅读 245评论 0 2