详解单例模式

概述

单例

简单来说,单例模式的作用就是保证整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个或者不存在

单例模式确保某个类只有一个实例,而且自行实例化,并向整个系统提供整个实例的单例模式。

如果要写一个单例模式的代码第一件事情就是自己要清除你到底写的是饿汉式还是懒汉式的单例

饿汉式: 不管用不用,先提前创建对象。(使用不当线程不安全、节省空间)
懒汉式: 延迟加载,用到的时候再去创建对象。(线程安全,浪费内存)

三种线程安全的懒汉式(延时加载)单例写法

写一个线程安全的单例模式最重要的三点:

  • 构造方法要私有
  • 静态成员变量存储唯一的实例化对象
  • 提供静态的公开的方法去获取实例对象

静态内部类的单例写法

public class SingletonStatic {

    //构造方法要私有
    private  SingletonStatic(){}

    //唯一的静态实例
    private  static  class   SingletonFactory{
        private  static   SingletonStatic  one = new SingletonStatic();
    }

    //公开方法
    public  static  SingletonStatic  getSingleton(){
        return   SingletonFactory.one;
    }

}

初始化安全

简单来说,在jvm类加载过程中的初始化阶段,也就是虚拟机执行<clinit>()方法的过程中是线程安全的,虚拟机只允许一个线程去执行<clinit>()方法,其他线程都会被阻塞。详细类加载过程可以看另一篇虚拟机的类加载过程,又因为它是静态内部类,只会被初始化一次,所以它满足单例的要求。
静态内部类问题

饿汉式与懒汉式的区别式在于对对象创建的时机,对于静态内部类来说,只有在第一次调用getSingleton()方法的时候才会去初始化静态内部类,实例对象才会被创建,所以说它是属于饿汉式的。
枚举单例写法(最简单)

public enum SingletonEnum {
    ONE;
    public void  say(){
        System.out.println("枚举单例测试"+this.hashCode());
    }
}

双重检查锁(DCL)写法(最重要的)
我们来看这段代码

代码1

这段代码看似符合单例模式,但其实只有在单线程下它确实是没有问题的,我们考虑一下,当50个线程同时到达箭头所指方向的时候,如果不加锁,那么就会在Java堆中创建50个one对象所需的内存大小,但由于它是静态对象,在栈中只会有一个该对象的符号引用,所以就有49个内存大小会被浪费,着显然是不合理的。这个问题如何解决呢?最简单的就是使用Synchronized。
代码2

这样上面的问题就解决了,当同时50个线程到达加锁的方法的时候,将发生如下的情况:
多线程加锁执行

这样又会造成多个线程抢占到CPU时间片的时候去发呆,CPU的时间是非常非常宝贵的,这样浪费显然又不合理,那么我们接着优化,我们看看下面两个代码:
代码4

这个代码其实和代码3是一样的,既然是优化,当然不能这样,我们来看看代码5
代码5

为什么加第一次判断呢,这里很明显只有第一次才能进入下面的代码块,而其他线程直接放回的就是one对象,这样很大程度上节省了cpu发呆时间。
那么为什么加第二次的判断呢,命名加了synchronized只有一个线程才可以执行这段代码啊?我们来看去掉后的代码

public class SingletonDCL {
    //构造私有
    private  SingletonDCL(){}
    //私有的静态变量存储唯一实例,懒汉,暂不初始化
    private  static   SingletonDCL   one = null;
    //公开方法
    public  static   SingletonDCL   getSingleton(){
        //没有对象再去创建
        if(one!=null){
            synchronized (SingletonDCL.class) {
                one = new SingletonDCL();
            }
        }
        return  one;
    }
}

这里假设5个线程同时来竞争锁,线程一先拿到锁,实例化对象完成后,这时候线程二又拿到锁,实例化对象,这样的话又同时创建了4个无用对象。和上面的情况是一样的。因次双层检查锁两次检查都必不可少。

我们对代码5再假设:线程一执行到了one = new SingletonDCL();这里注意这段代码并不是一个指令,这时线程二执行到了第一个判断处,而这里线程一new过程中new了,此时one不为null,但是new这个过程又没有执行完成(这部分可以了解对象的实例化过程),而线程二直接返回了不完整的one对象,这就有了问题,这就是我们常说的有序性的问题。

解决办法大家应该也都知道,就是volatile关键字,它能够使得jvm进制指令重排序。

因此最终版的双检索单例模式代码如下:

public class SingletonDCL {
    //构造私有
    private  SingletonDCL(){}
    //私有的静态变量存储唯一实例,懒汉,暂不初始化
    private  static   volatile SingletonDCL   one = null;
    //公开方法
    public  static   SingletonDCL   getSingleton(){
        //没有对象再去创建
        if(one!=null){
            synchronized (SingletonDCL.class) {
                one = new SingletonDCL();
            }
        }
        return  one;
    }
}


如何破坏一个单例模式

  • 反射破坏单例模式
  • 反序列化破坏单例模式

其实枚举就是最简单的且天然支持反射攻击的一种单例模式。
接下来附上一个可以防止序列化攻击破坏单例模式的完整的双检索双检索单例模式

public class SingletonDCL {
    //构造私有
    private  SingletonDCL(){}
    //私有的静态变量存储唯一实例,懒汉,暂不初始化
    private  static   volatile SingletonDCL   one = null;
    //公开方法
    public  static   SingletonDCL   getSingleton(){
        //没有对象再去创建
        if(one!=null){
            synchronized (SingletonDCL.class) {
                one = new SingletonDCL();
            }
        }
        return  one;
    }
  private  Object  readResolve(){
     return  instance;
   }
}



简单分析并发编程中可见性、有序性、原子性
有序性

有序性:在微观上指的是CPU指令的执行顺序,宏观上指的是java代码的执行顺序。
当两段代码的执行结果没有必然联系的话,JVM内部可能会对其进行指令重排序,这是JVM自身优化的一种策略。

原子性

原子性就是需要保证某个操作是一个原子操作:实现方式synchronized、lock锁。

可见性(注意的是只有多核系统才会有可见性的问题)

可见性问题

每次CPU都从内存中读取数据到CPU缓存,当进行i++操作时,CPU1从内存读取i=10,进行+1操作,这时候CPU1缓存中i=11,此时内存中i=10,而CPU2从内存再读取的话又是读取到了10,进行+1操作的话CPU2缓存中又是11

其实这个时候正确的应该时CPU2读取到11再+1,i的值应该时12,这就是可见性的问题(额外:MESI协议,保证CPU见缓存一致性问题)

volitale关键字:被这个关键字的修饰的变量,当CPU在读数据时,必须保证其他CPU缓存中的最新值被回刷回内存中去,这样的话就能保证每次CPU读取的值都是最新值了。

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

推荐阅读更多精彩内容