看了这篇文章,你能和面试官畅谈单例模式

一、前言

最近看了很多的书还有视频,他们都花了很长的篇幅提到了单例模式,于是我想把他们都总结起来,写下这篇文章。目的就是,让小白能搞懂单例模式,以及==单例模式的经典面试题==。为什么说是小白也能懂的呢?哈哈哈,还不是小胖也是一个小白~~~

二、单例模式的解释

单例模式定义:一个类只能有一个实例,且该类能自行创建这个实例的一种模式。其实单例模式在C#或者.NET里面更好理解,像win7的任务管理器,在系统中只能创建一个。有些理解了嘛?

单例模式只能有一个实例,实例化其实就是new的过程,是不可能阻止他人不去用new的。所以我们完全可以直接就把这个类的构造方法改成私有的。对于外部的代码,不能用new来实例化他,我们完全可以再写一个public方法,叫做getInstance(),这个方法的目的就是返回一个实例,但是在这个方法中,我们需要是否实例化的判断。

来一个简单的例子
package singleton;

/**
 * 描述: 懒汉式(线程不安全)
 * **/
public class Singleton3 {
    private static Singleton3 instance;

    private Singleton3(){

    }
    //如果两个线程同时到达,会出现线程不安全的情况
    public static Singleton3 getInstance(){
        if (instance == null){
            instance = new Singleton3();
        }
        return instance;
    }
}

单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。可以节省内存和计算、保证结果正确、方便管理。适用场景是无状态的工具类、全局信息类。

==记住,上面的单例模式是线程不安全的。==

三、实现单例模式的8种写法

1.饿汉式(静态常量)(可用)
package singleton;

/***
 * 描述:饿汉式(静态常量)  (可用)
 * **/
public class Singleton1  {
    private final static Singleton1 INSTANCE = new Singleton1();

    private Singleton1(){

    }
    public static Singleton1 getInstance(){
        return INSTANCE;
    }
}

上面是饿汉式的静态常量的写法,可以看到类在加载后就完成了实例化的创建。优点:写法简单,类加载后就完成了实例的创建。缺点:提前占用系统的资源。

2.饿汉式(静态代码块)(可用)
package singleton;

/***
 * 描述:饿汉式(静态代码块)  (可用)
 * **/
public class Singleton2  {
    private final static Singleton2 INSTANCE;

    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2(){

    }
    public static Singleton2 getInstance(){
        return INSTANCE;
    }
}

上面是饿汉式的静态代码块的方式,只不过和第一种有一些区别。优缺点和第一种是一样的。优点:写法简单,类加载后就完成了实例的创建。缺点:提前占用系统的资源。

3.懒汉式(线程不安全)(不可用)
package singleton;

/**
 * 描述: 懒汉式(线程不安全)
 * **/
public class Singleton3 {
    private static Singleton3 instance;

    private Singleton3(){

    }
    //如果两个线程同时到达,会出现线程不安全的情况
    public static Singleton3 getInstance(){
        if (instance == null){
            instance = new Singleton3();
        }
        return instance;
    }
}

上面的懒汉式的写法,它是线程不安全的,因为在多线程的时候,可能会有两个线程同时到达instance==null,然后都会初始化,这就创建了两个对象了,是线程不安全的,不能使用。

4.懒汉式(线程安全)(不推荐)
package singleton;

/**
 * 描述:懒汉式(线程安全)(不推荐)
 * */
public class Singleton4 {
    private  static  Singleton4 instance;
    private Singleton4(){

    }
    //但是效率不高
    public synchronized static Singleton4 getInstance(){
        if (instance == null){
            instance = new Singleton4();
        }
        return instance;
    }
}

上面是懒汉式的第二种写法,这种方法是线程安全的,但是不推荐使用,因为效率是低下的。在getInstance上面加上了synchronized的同步方法,那么只能有一个线程可以进入到这个方法中,但是在多线程的时候效率是非常低的,因为任何一个线程进入的这个方法,都需要去等待锁的释放,所以不推荐使用。

5.懒汉式(线程不安全)(不可用)
package singleton;

/**
 * 描述:懒汉式(线程不安全) (不推荐)
 * */
public class Singleton5 {
    private static Singleton5 instance;

    private Singleton5(){

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

上面是懒汉式的第三种写法,是对第二种的改进,不在锁在方法上,加在创建对象上面,但是这可能会引发线程安全的问题。如果连个线程都判断instance==null,都进入到if里面,这时只有一个线程会运行,但是第一个线程执行完成之后,第二个线程还是会创建实例,那么就是线程不安全的。

6.懒汉式(双重检查)(推荐面试使用)
package singleton;

/**
 * 描述: 双重检查
 * 优点: 线程安全;延迟加载;效率较高
 * 为什么要double-check
 * 1.线程安全
 * 2.单check为什么不行?
 * 3.放在判断后面会引发线程安全问题
 * 4.单层锁,但是synchronized放在方法上,这样可以,但是会导致性能问题
 *
 * 为什么要用volatile
 * 1.新建对象实际上有3个步骤(分配内存资源,调用构造函数,将对象指向分配的内存空间)新建对象不是原子操作。
 * 2.JVM重排序会带来NPE(空指针的问题)
 * 3.防止重排序
 * */
public class Singleton6 {
    private volatile static Singleton6 instance;
    private Singleton6(){

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

上面就是很有名的double-check单例模式了,它的优点有:线程安全;延迟加载;效率较高。它是线程安全的,而且效率很高,推荐我们在面试的时候使用。这个代码同时也引出了我们在面试过程中的2个问题。
==懒汉式单例模式为什么要用double-check,不用就不安全吗?懒汉式单例模式为什么双重检查模式要用volatile?==

7.懒汉(静态内部类方式)(可用)
package singleton;

/**
 * 描述: 静态内部类方式,可用
 * 懒汉
 * ***/
public class Singleton7 {
    private Singleton7(){
    }
    private static class SingletonInstance{
        private static final Singleton7 INSTANCE = new Singleton7();
    }
    public static Singleton7 getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

上面是静态内部类的方式,是可以推荐使用的,而且效率也是可以的。外部类加载,JVM不会创建多个实例。

8.枚举 推荐在项目中使用
package singleton;

/**
 * 描述:单例模式:枚举 推荐用
 * */
public enum Singleton8 {
    INSTANCE;

    public void whatever(){
    //无论什么方法
    }
}

上面是枚举方式的单例模式,是生产实践中最佳的单例模式的写法,同时可以防止反序列化破坏单例。

四、常见面试问题

什么叫单例设计模式

答:单例模式的重点在于整个系统上共享一些创建时较耗资源的对象。整个应用只维护一个特定类实例,它被所有组件共同使用。Java.lang.Runtime是单例模式的经典例子。

你知道饿汉式的缺点吗?

答:饿汉模式,类一加载的时候就会实例化对象,所以要提前占用系统资源。

那懒汉式的缺点呢?

答:不会出现占用资源的问题,但是需要使用合适,否则会带来线程安全问题。

懒汉式单例模式为什么要用double-check,不用就不安全吗?

答:为了线程安全,我们需要使用double-check。

追问,单check为什么不行?(代码见5.懒汉式)

答:单check是线程不安全的(代码见5.懒汉式),可能会有多个线程走到了 if (instance == null)的里面,由于synchronized看似只能有一个线程会创建对象,但是第二个也会创建。

追问,你可以把synchronized写在方法的外面呀?(代码见4,。懒汉式)

答:这个是可以解决线程安全的问题,但是效率不是很高,每个线程都需要等待锁的释放,会导致性能的问题,不推荐使用。

你说为什么要用volatile?

答:在多线程的时候,创建对象分为3步,CPU可能会重排序,首先建一个空的对象,然后复制给引用,然后调用构造方法。
第一个线程进来了,第一个对象已经不是空的,但是构造方法没有执行,里面的属性是空的。第二个线程发现不是空的,就会直接跳过创建实例的方法,之后再使用的时候引发的问题。使用volatile可以避免这个问题,对于第二个线程来说,他的创建过程对第一个线程来说是可见了,他就会等待创建完成。

那这么多应该如何选择,用哪种单例的实现方案最好?

答:枚举方式的单例模式,是生产实践中最佳的单例模式的写法,同时可以防止反序列化破坏单例。

那你知道happens-before原则嘛?

答:volatile就是happens-before原则呀......==未完待续==

请用Java写出线程安全的单例模式。

答:上面已经很多例子了,小胖觉得可以用第6种double-check。

五、关于几种解法的选择

《剑指offer》上面的推荐给面试官的解法是1.饿汉式(静态常量)(可用)和7.懒汉(静态内部类方式)(可用)。
《大话设计模式》上面的推荐也是1.饿汉式(静态常量)(可用)
《线程八大核心+Java并发底层原理精讲》上面推荐使用6.懒汉式(双重检查)(推荐面试使用)

小胖觉得可以使用第6种,这可能会==打开一些面试的问题==,把问题引入到我们了解熟悉的方向。当然你在手写单例模式的时候,可以去询问一下要求,是需要饿汉式还是懒汉式。

六、参考资料

书籍1:《大话设计模式》 第21章 有些类也需计划生育--单例模式
书籍2:《剑指offer》 面试题2:实现Singleton模式
视频:《线程八大核心+Java并发底层原理精讲》

七、关于本系列的解释

本系列想制作23种设计模式+7种设计原则一系列课程,其目的就是一个简单的记录学习的过程。不知道能帮助到多少人,也不知道技术是否会有一定的深度。

==制作不易,您的点赞是我最大的动力。点赞10个,我会发出下一篇文章==

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

推荐阅读更多精彩内容