Java面试之单例模式浅谈

单例模式是Java面试中常会问到的一个问题,众所周知,单例模式分为两大部分:饿汉模式和懒汉模式。但是,如果当面试官问道关于单例模式的话,如果你只答出这两种模式,且懒汉模式还是最基础最简陋版的话,那么你可能就要悲剧了。

当被问到单例模式的时候我们到底需要知道哪些知识点呢?接下来我根据我所掌握的知识点进行一些总结,希望对大家能够有所帮助。

一、饿汉模式:

其实从名字就可以辨认出,该模式的写法是在定义私有全局变量时直接初始化变量,饿汉么,既然已经饿了,那就不管什么赶紧吃了,附代码如下:

private static Singleton singleton = new Singleton();

public static Singleton backSingletonAction() {

return singleton;

     }

该模式最大的问题是,私有全局变量singleton的创建时机问题,由于被static修饰,当class被加载的时候,singleton就会被创建出来,那么就有可能造成空间浪费,一个单例类就会存在一个对象,那么如果有100个单例类就回有100个对象,但是有可能只用到1个,这种情况下就会有99个对象的空间被浪费,这就是饿汉模式最大的问题。

二、懒汉模式:

顾名思义,这种模式的单例比较懒,只有在用到的时候对象才会被创建出来,可以很大程度的节约空间。懒汉模式的实现由很多种,接下来我就从最基础的写法开始进行分析。

1、最简陋的懒汉模式:

private static Singleton singleton = null;

    public static Singleton backSingletonAction() {

if (null == singleton) {

singleton = new Singleton();

}

return singleton;

}

如你所见,这个就是最简陋的懒汉模式单例的书写方法,为什么说是最简陋的单例模式?线程安全!没错,就是存在线程安全问题。当多条线程对backSingletonAction进行访问的时候,就可能造成在singleton成功创建出对象之前有多条线程进入到if判断中,从而造成singleton对象被多次赋值的问题。针对该问题提出设想,这种写法的问题原因是线程不安全,那么让它线程安全不就好了么!如何做到线程安全?当然是使用synchronized了,于是就有了第二种方案:

2、最慢的线程安全的懒汉模式:

private static Singleton singleton = null;

    public static synchronized Singleton backSingletonAction() {

if (null == singleton) {

singleton = new Singleton();

}

return singleton;

}

这种单例的书写方式,由于添加了synchronized关键字,所以线程是安全的,但是也是由于synchronized关键字造成了backSingletonAction加锁的问题,每有一条线程需要访问backSingletonAction时就需要进行锁的竞争,没有竞争到锁的线程需要在锁外部进行等待,等待当前持有锁的线程执行完锁内全部的内容后将锁释放,这样相当于将单例模式中backSingletonAction方法变为了单线程模式,这对于速度是有很大的影响。所以针对这个问题再次进行优化。

3、相对完美版的懒汉模式:

  private static Singleton singleton = null;

  public static Singleton backSingletonAction() {

if (null == singleton) {

synchronized (Singleton.class) {

if (null == singleton) {

singleton = new Singleton();

}

}

}

return singleton;

}

这种单例的书写模式,针对之前的方法进行的改进,使用了双重检查锁的方式,当多条线程第一时间访问到backSingletonAction方法时,进行第一条if语句的判断,此时由于singleton还未初始化,所以if条件为true,均可进入到if条件中,但是,在进入第一层if条件后的线程将会对synchronized进行竞争,只有一条线程可以竞争到锁并进入,那么为什么在锁中仍然要添加if条件判断呢?那是因为在singleton为初始化时进入第一层if的条件可能过多,所以导致在synchronized关键字竞争中仍有很多线程在第一层if内和synchronized外进行等待,当第一条进入synchronized的线程执行完synchronized内部的代码后,若对后续竞争synchronized的线程不进行if判断还是会造成重复创建的问题。

其实这样看起来就已经可以完成这个懒汉模式的单例了,但是为什么这个模式的单例会被称之为相对完整版的懒汉模式?那是因为这种懒汉模式的单例仍然不够完美,我们还可以对其进行优化。

4、完美版懒汉模式单例:

private static volatile Singleton singleton = null;

    public static Singleton backSingletonAction() {

if (null == singleton) {

synchronized (Singleton.class) {

if (null == singleton) {

singleton = new Singleton();

}

}

}

return singleton;

}

为什么说这种写法是完美版的懒汉模式?4和3的区别又在哪里?其实细心地小伙伴已经看出来了,这一版本其实只是在定义singleton的时候加了volatile进行修饰。

volatile作用有两个:

1、禁止指令重排序

2、禁止CPU缓存

而这里使用了volatile,主要是要利用volatile进制指令重排序的功能。

这里可能需要讲一下指令重排序是什么(如果知道的可以将这一部分略过):

在java程序执行过程中,JVM虚拟机可能会根据代码进行执行顺序的优化,即在不影响运行结果的前提下,下一行的代码可能会优于当前行的代码进行执行,例:

①int a = 0;

②String b = "hello";

③System.out.println(a);

④System.out.println(b);

在执行①和②语句上没有必然联系,b="hello"并不依赖于a=0,所以①和②在执行过程中有可能会被JVM优化而导致先执行②再执行①,但是①和③,②和④之间是有因果关系的,所以JVM不会对①和③,②和④的执行顺序进行改变,当代码执行顺序发生变化时,就是指令重排序。

为什么指令重排序会对3的书写方式造成影响?这又涉及到了JVM创建对象的步骤问题,当JVM创建对象时主要分以下三步:

1)开辟内存空间(new指令)----此时已有内存地址了

2)给对象初始化----只是对象成员变量去初始化默认值

3)将堆空间的内存地址(即引用地址)赋值给栈空间的本地变量表中的引用(reference)

在这三步中2)和3)就有可能发生指令重排序,那么当这种指令重排序发生了,并且很幸运此时,由于时间片的切换,切换到第一步if判断的线程时,会发生什么?此时变量是已经存在引用了,所以null==singleton的判断为false,所以会直接return  singleton,但是此时的singleton还未初始化默认值,考虑一下接下来会发生的恐怖事情吧。这种情况发生概率极低且不可复现,但是不代表没有。

所以利用volatile进制对singleton的指令重排序,就可以避免这种事情的发生。

5、静态内部类实现懒汉模式单例

private static class SingletonFactory {

private static Singleton singleton = new Singleton();

private SingletonFactory(){};

public static Singleton backSingleton() {

return singleton;

}

}

public static Singleton backSingletonAction() {

return SingletonFactory.backSingleton();

}

    静态内部类是利用了,类加载的时候不会加载类的静态内部类,但是会加载其私有静态成员变量的原理实现的,程序启动时,加载单例类,由于SingletonFactory为其静态内部类,所以不会被加载,从而不会加载singleton,当调用backSingletonAction获取单例对象时,则会加载SingletonFactory,在加载SingletonFactory时,由于singleton是SingletonFactory的静态成员变量,所以会加载singleton并初始化。


    其实无论是静态内部类还是双重检查锁格式,仍然是有缺陷的,因为这两种书写格式是可能被利用反射或序列化破坏其单例格式,创建出不唯一的对象,如果不想被其他人通过反射或序列化的方式进行破坏,那么可以选用枚举方式来实现单例,枚举由于继承自抽象类ENUM,所以不能通过反射创建,想知道如何通过枚举创建单例模式其实可以再网上找一找,很多。


    最后,希望大家无论是在工作中还是在面试时都能取得更好的成绩。

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

推荐阅读更多精彩内容

  • 摘要:设计模式之一:单例模式目录介绍1.单例模式介绍2.单例模式定义3.单例模式使用场景4.单例模式的实现方式 4...
    肆虐的悲傷阅读 458评论 0 2
  • 单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。 因为设计模式讲究对象之间的关系的抽象,...
    yangjingqiang阅读 216评论 0 0
  • 写在前面 前段时间在回顾 Java 当中的 23(泛指并非只有23) 种设计模式,最近又在学习 Kotlin ,然...
    汪海游龙阅读 1,957评论 0 2
  • 单例设计模式全解析 在学习设计模式时,单例设计模式应该是学习的第一个设计模式,单例设计模式也是“公认”最简单的设计...
    WekingZhang阅读 294评论 0 1
  • 网络营销之所以越来越受到重视一个主要的原因就是因为“精准”。相比较传统媒体的陈旧广告形式,网络营销能为广告主带来更...
    品牌笔记阅读 2,256评论 0 4