JAVA中的单例模式

在java中,单例模式的实现方法有如下几种:

1.饿汉模式

所为饿汉模式,即一开始就创建一个静态的对象,之后该对象一直存在。这种模式不会有线程安全问题。

package com.dhb.builder.singleton;

public class Singleton1 {
    
    private static Singleton1 instance = new Singleton1();

    private Singleton1() {
    }
    
    public static Singleton1 getInstance() {
        return Singleton1.instance;
    }

}

2.懒汉模式

对于饿汉模式,优点在于实现简单。但是存在一个问题就是 instance 只要 Singleton1被加载就会被创建到static所在的静态方法区。如实现数据库连接池等情况,用这种方法一上来就要创建数据库的连接资源,实际系统中暂不使用。这就造成了资源的浪费。因此,对于这种情况,出现了与之对应的懒汉模式。
即一开始并不创建对象,待需要使用时再new。

package com.dhb.builder.singleton;

public class SingletonDemo1 {
    
    private static SingletonDemo1 instance = null;
    
    private SingletonDemo1() {
        
    }

    /**
     * 存在线程安全问题
     * @return
     */
    public static SingletonDemo1 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo1();
        }
        return instance;
    }
}

这是大家想到的最常用的懒汉模式的写法。但是问题来了,上述模式在多线程的情况下是线程不安全的!也就是说,如果有两个线程,同时getInstance(),同时都会判断instance的值为null。这种情况下会创建多个实例。
为了解决上述问题,我们引入了锁:

package com.dhb.builder.singleton;

public class SingletonDemo2 {

    private static SingletonDemo2 instance = null;

    private SingletonDemo2() {
        
    }

    /**
     * 增加同步机制,解决线程安全
     * @return
     */
    public static synchronized SingletonDemo2 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

上面这种做法,确实解决了线程安全问题,但是带来了一个更加不好的问题,那就是每一次请求都会加锁!这样会严重影响性能。更好的做法是采用双重检查机制:

package com.dhb.builder.singleton;

public class SingletonDemo3 {

    private static SingletonDemo3 instance = null;

    private SingletonDemo3() {
        
    }

    /**
     * 增加双重检查机制,解决synchronized效率问题
     * @return
     */
    public static  SingletonDemo3 getInstance() {
        if(instance == null)
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        return instance;
    }
}

上述单例实际仍然存在问题,那就是类初始化仍然需要时间,如果同时又两个线程同时进入getInstance方法,第一个线程锁定之后,第二个线程判断不为空,则直接使用instalce,如果此时第一个线程对SingletonDemo3对象还没实例化完成,如该对象内部存在一个耗时的引用,如果是一个数据库连接,则会导致第二个线程使用的对象不完整。出现空指针。因此更好的写法是加上volatile。以保证happen-before原则。

package com.dhb.builder.singleton;

public class SingletonDemo4 {

    private volatile static SingletonDemo4 instance = null;

    private SingletonDemo4() {
        
    }

    /**
     * 增加双重检查机制,解决synchronized效率问题
     * @return
     */
    public static SingletonDemo4 getInstance() {
        if(instance == null)
            synchronized (SingletonDemo4.class) {
                if (instance == null) {
                    instance = new SingletonDemo4();
                }
            }
        return instance;
    }
}

这样单例模式才完全解决。上述方法比较冗繁,有没有更好的解决办法呢,有幸阅读过《effective java》这本书对于单例有更好的解决办法。

3.更好的解决办法

第一种方法,利用静态内部类:

package com.dhb.builder.singleton;

import java.util.stream.IntStream;

public class SingletonHolder {

    private SingletonHolder() {

    }
    private static class InstanceHolder{
        private final static SingletonHolder INSTANCE = new SingletonHolder();
    }

    public static SingletonHolder getInstance() {
        return InstanceHolder.INSTANCE;
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(1,100).forEach(i -> new Thread(String.valueOf(i)){
            @Override
            public void run() {
                System.out.println(SingletonHolder.getInstance());
            }
        }.start());
    }
}

上述方法执行结果:

com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6

可以看出SingletonHolder类只实例化了一次。这种方法很巧妙地利用一个内部类,很简单的代码即实现了单例,而且是线程安全。

方式二:《effective java》中还有一种更简单的写法,那就是枚举。也是《effective java》作者最为推崇的方法。

package com.dhb.builder.singleton;

import java.util.stream.IntStream;

public class SingletonEnum {

    private SingletonEnum() {

    }

    private enum Singleton {
        INSTANCE;

        private final SingletonEnum instance;

        Singleton() {
            instance = new SingletonEnum();
        }

        public SingletonEnum getInstance() {
            return instance;
        }
    }

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

    public static void main(String[] args) {
        IntStream.rangeClosed(1,100).forEach(i -> new Thread(String.valueOf(i)){
            @Override
            public void run() {
                System.out.println(SingletonEnum.getInstance());
            }
        }.start());
    }
}

上述方法执行结果:

com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6

在java中,枚举天然实现了单例模式。其构造方法只会实例化一次。

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

推荐阅读更多精彩内容

  • 之前在面试中,被面试官问到了设计模式,很自信的说了解单例模式。然后问我知道哪些实现方式,说出你觉得最好的是哪一种方...
    发霉的荷尔蒙2阅读 1,285评论 11 32
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,222评论 4 34
  • 前言   在GoF的23种设计模式中,单例模式是比较简单的一种。然而,有时候越是简单的东西越容易出现问题。下面就单...
    Skymiles阅读 2,584评论 2 3
  • 1.设计模式 1.1 什么是设计模式 设计模式是一套被反复利用,多数人知晓的,经过分类编目的,代码设计经验的总结。...
    端木轩阅读 211评论 0 0
  • 哈哈
    破旧脾气阅读 206评论 0 0