单例模式

Singleton单例模式简介

在应用单例模式时,单例对象的类必须保证只有一个实例存在.许多时候整个系统只需要一个全局对象, 这样有利于协调系统整体行为,如在一个应用中,应该只有ImageLoader实例,这个ImageLoader中又含有线程池,缓存系统,网络请求等, 很消耗资源,所以不能多次构建实例.

构建单例模式要保证几点:

  • 构造方法不对外开放,一般为private
  • 通过一个静态方法或者枚举返回单例类对象
  • 确保单例对象有且只有一个,尤其在多线程的环境下.
  • 确保单例对象在反序列化时不会重新构建对象.

构建单例模式的方式:

  • 懒汉式: 指全局的单例实例在第一次被使用时构建
  • 饿汉式: 指全局的单例实例在类转载时构建.

懒汉式单例

单线程的懒汉式单例:

    public class Singleton {
    
        private static Singleton sInstance;
    
        private Singleton() {
        }
    
        public static  Singleton getInstance() {
            if (sInstance == null) {
                  sInstance = new Singleton();
             } 
            return sInstance;
        }
    }

这种最简单的懒汉式单例只有在单线程中才有作用, 如果在多线程中由于多线程执行的问题的会因为线程并发的问题产生多个实例.所以我们需要同步即:

    public class Singleton {
    
        private static  Singleton sInstance;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (sInstance == null) {
                synchronized (Singleton.class) {      
                        sInstance = new Singleton();
                }
            }
            return sInstance;
        }
    }

但这种线程同步还是不能做到线程安全的问题, 还是会产生多个实例对象, 所以我们再一次进行判断nul:

    public class Singleton {
    
        private static  Singleton sInstance;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (sInstance == null) {
                synchronized (Singleton.class) {
                    if (sInstance == null) {
                        sInstance = new Singleton();
                    }
                }
            }
            return sInstance;
        }
    }

这种双重判断DCL单例在JDK小于1.5时还是会为因为构造对象出现指令重排序的问题, 故而给单例对象添加volatile关键字修饰, 禁止指令重排序; 所以最终版为:


    public class Singleton {
    
        private static volatile Singleton sInstance;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (sInstance == null) {
                synchronized (Singleton.class) {
                    if (sInstance == null) {
                        sInstance = new Singleton();
                    }
                }
            }
            return sInstance;
        }
    }

从上面代码中可以看到:

  • 构造方法私有化;
  • 单例对象使用volatile修饰;
  • 使用静态方法getInstance()返回单例对象;
  • 在构建单例对象使用Double-CheckLock(DCL);

使用DLC双重检查,第一次判断sInstance=null,是为了避免不必要的同步问题,第二次判断sInstance=null是为了避免多次创建实例对象.其实在构建实例sInstance=new Singleton()时, 这句代码并不是一个原子操作,可以分为三步:

  • 给Singleton实例分配内存
  • 调用Singleton()构造函数,初始化成员变量字段
  • 将sInstance对象指向分配的内存空间.

但是由于指令重排序的原因,可能不保证上述三点不按照顺序执行,可能是1-2-3,也可能是2-1-3或者1-3-2,如果是第三点先执行,第二点还未执行,就切换到其他线程,sInstance已经非空,就不会构建实例,使用时就会崩溃.

知道是指令重排序造成的问题后,只要禁止指令重排序既可, 就可以使用volatile关键字修饰sInstance,保证单例对象内存可见性.

饿汉式单例


    public class Singleton{
        
        private static final Singleton sInstance= new Singleton();
        
        private Singleton(){};
        
        public static Singleton getInstance(){
            return sInstance;
        }
        
    }

饿汉式单例存在的特点也很明显: 由于sInstance实例在类加载时进行的,而类的加载是由ClassLoader来进行,故而实例的初始化时机比较难把握,可能由于初始化时机太早,造成资源浪费,如果初始化本身依赖一些其他数据,那么很难保证其他数据在这之前已经准备就绪.

那么什么时候会类加载? 不太严格的说,类的加载一般会出现在一下几个时机:

  • new一个对象是;
  • 使用反射创建实例时;
  • 子类被加载时,如果父类还未加载,就先加载父类
  • JVM启动执行的主类会首先被加载.

静态内部类单例模式

DCL双重检查单例模式虽然在一定程度上解决了资源消耗,多余的同步,线程安全问题,但是他还是会出现失效问题, 这种问题被称为双重检查锁定(DCL)失效,建议使用如下代码:


    public class Singleton {
    
        private Singleton(){};
        
        public static Singleton getInstance(){
            return  SingletonHolder.sInstance;
        }
        
        private static class SingletonHolder{
            private static final Singleton sInstance= new Singleton();
        }
    }

当第一次加载Singleton类并不会初始化sInstance,只有在第一次调用Singleton的getInstance()才会初始化sInstance.

枚举单例

    
    public enum SingletonEnum{
        INSTANCE;
        public void doSomething(){
            //...
        }
    }

枚举单例的最大优势在于,无偿提供了序列化机制,绝对防止对象实例化,即使在面对复杂的序列化或者反射攻击的时候.

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

推荐阅读更多精彩内容