单例模式2-懒汉式

懒汉式,跟饿汉式相反,类加载的时候并不会被实例化,而是在第一次被调用的时候被实例化。

懒汉式单例奥义

  • 构造器私有!!!
  • 暴露getInstance()方法!!!
  • 在第一次被调用的时候进行初始化

写法1(存在线程安全问题喔)

public class LazySimpleSingleton {

    private static LazySimpleSingleton lazy = null;

    /**
     * 构造器私有!
     */
    private LazySimpleSingleton(){}

    /**
     * 对外暴露的实例化接口
     * @return
     */
    public static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }

}

自己多线程跑一下,打印一下就知道不是一个对象了。

改进1(synchronized搞他)

    public synchronized static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }

再多线程跑一下,好像解决了一样,其实并不是解决的很好,在更多线程的情况下,会产生大量阻塞,性能会下降。

改进2(双重检查锁)

    /**
     * 双重检查锁
     * @return
     */
    public synchronized static LazySimpleSingleton getInstance3(){
        // 第一重检查
        if(lazy == null){
            // 锁class
            synchronized (LazySimpleSingleton.class){
                // 第二重检查
                if(lazy == null){
                    lazy = new LazySimpleSingleton();
                }
            }
        }
        return lazy;
    }

把锁的范围变小了,只会在多个线程抢着去new才会产生点点的阻塞,所以只要锁的内容不多,调用者感知不强。

  • 第一重检查好说,就是看是不是为null,需不需要进行new
  • 第二重则是因为可能很多个线程同时通过了第一重检查,想象一下去掉第二重会发生什么事情(只要经过第一重,都会new一次),所以需要第二重检查。

隐患

看上去该改进2又单例,性能也不差,然而还隐藏着一个问题:指令重排序
正常new的时候干了什么?

  1. 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间
    然而有的编译器为了提高性能,会将2和3对调:
  4. 分配内存空间
  5. 将对象指向刚分配的内存空间
  6. 初始化对象
    这样就会产生一个巨大的问题,有可能在第一次new的时候,只new到第二步指向内存,还没执行真正的初始化,这时候其他线程会经过第一重检查直接到return,然后拿到还没初始化的对象进行调用。

改进3 (双重检查锁——volatile防止指令重排序)

private volatile static LazySimpleSingleton lazy = null;

对的,就改一句,volatile可以防止指令重排序,所有的写操作都将发生在读操作之前,这是完整的懒汉式双重检查锁单例;

改进4 (静态内部类)

虽然说双重检查锁性能已经不差了,但是能不能再提高性能一点呢?从类初始化的角度考虑:
这里主要是用到了一点:内部类要在方法调用之前被初始化,意味着它能巧妙避开线程安全问题,而且代码简单。

public class LazyInnerClassSingleton {

    /**
     * 构造器私有
     */
    private LazyInnerClassSingleton() {
    }

    public static LazyInnerClassSingleton getInstance() {
        return LazyHolder.LAZY;
    }

    /**
     * 默认不加载该内部类
     */
    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }

}

改进5(不法分子用反射破坏单例)

我们知道反射可以破解private关键字,所以能够通过反射调用构造方法,具体如下:

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
        Constructor<LazyInnerClassSingleton> c = clazz.getDeclaredConstructor(null);
        c.setAccessible(true);
        LazyInnerClassSingleton lazy1 = c.newInstance();
        LazyInnerClassSingleton lazy2 = LazyInnerClassSingleton.getInstance();
        System.out.println(lazy1 == lazy2);//false
    }

那解决调用构造器来搞事情的反射也很简单,只要在构造方法加个判断就行:

private LazyInnerClassSingleton() {
    if (LazyHolder.LAZY!=null){
        throw new RuntimeException("不准搞事");
    }
}

重新执行上面的测试代码,可以发现反射搞不了事了。

不算改进(序列化破坏单例)

有时候(很少,我几乎没见过,当做没有)会把对象序列化写到磁盘,到时候拿出来用,这样做如果是单例对象,就破坏单例了。
太长不写...

注册式单例(两种:枚举、容器)

枚举单例模式

public enum SingletonEnum {
    /**
     * 单例
     */
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

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

进行测试:

  1. 调用getInstance()方法,把单例对象塞到data里面
  2. 序列化枚举对象
  3. 反序列化枚举对象
  4. 比较是否同一个对象
    结果是同一个对象

为何?
反编译发现INSTANCE被静态代码块初始化了,是一种饿汉式

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

推荐阅读更多精彩内容