03.设计模式-单例模式

单例模式的几种形式

1.饿汉式:优点是线程安全,在加载类的时候就创建一次并且只会创建一次,缺点是还没有使用就加载好了,是典型的空间换时间,当类装载的时候就会创建类实例,即使你很长时间用不到它,不符合java的设计原则

public class HungrySingleMode {
    
    private HungrySingleMode(){}
    
    private static HungrySingleMode instance=new HungrySingleMode();
    
    public static HungrySingleMode getInstance(){
        return instance;
    }
}

2.懒汉式,在使用的时候去创建实例,是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间,懒汉式有两种写法,关键在于加锁的位置

第一种写法,把锁加在创建实例的方法上,可以保证线程安全,但是效率较低,因为每次获取实例都要进行锁的操作,而实际上,只有第一次创建实例的时候需要加锁,在此调用因为实例已经存在,所以没必要再锁,这种方式降低了效率

public class LazySingleMode {

    private LazySingleMode(){}
    
    private static LazySingleMode instance;
    
    public static synchronized LazySingleMode getInstance(){
        if(instance == null){
            instance = new LazySingleMode();
        }
        return instance;
    }
}

第二种写法,把锁加载方法中,先进行实例的判断,如果为null,加锁创建,保证线程安全,如果不为null,直接返回实例,这样以来也就只有第一次获取实例的时候会进行锁的操作,以后就不需要了,提高了效率

public class LazySingleMode {

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

这样看来加了锁的懒汉式单例已经是绝对的线程安全了,其实不然
在高并发的多线程编程中,即便是如上我们已经加了锁,其实也不是绝对安全的,JDK1.5之后处理器针对乱序指令进行指令重排仍会导致这个问题,指令重排是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。编译器、处理器也遵循这样一个目标。注意是单线程。多线程的情况下指令重排序就会给程序员带来问题。

拿上边的懒汉式第二种写法为例,在进行instance是否为null的判断中

if(instance == null){
            synchronized (LazySingleMode.class){
                if(instance == null){
                    instance = new LazySingleMode();
                }
            }
        }

进行指令重排之后的代码为:

if(instance == null){
    synchronized (LazySingleMode.class){
        instance = new LazySingleMode();
    }
}

为什么第二个判断没有了呢,因为编译器判断这个程序在执行过程中,这个值是不会改变的,编译器不考虑多线程的情况,这就是指令重排的结果,多线程编程的情况下指令重排序就会给程序员带来问题。少了这个判断,就会导致,假设两个线程同时走到了if(instance == null)的下边,这时就会导致实例被创建两次

那么如何避免,接下来就是一种线程绝对安全的单例模式的写法,叫单例的DCL方式

public class LazySingleMode {

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

没有增加什么东西,仅仅是给LazySingleMode的实例添加了一个关键字:volatile

加了volatile,是告诉编译器,这个变量随时有可能会被其他线程改变,这样编译器就不会把这两个判断优化成一个判断了。也就是告诉编译器,不要自作聪明的对我进行指令重排序

网上对volatile的解释很详细:

volatile的变量,可以保证对该变量的操作具有原子性,典型的例子是long和
double型变量,通常需要分两步读写一个double变量,volatile修饰的double可以
保证对一个double变量的操作的两部分不会被多线程插入。以及对引用类型赋值
的,new一个实例的过程不会被其他线程插入(new在编译指令中是分成几步执
行的,防止这几步在执行过程中被其他线程取这个变量值,取到一个不完整的实
例)。可以简单的理解为对volatile变量的set和get方法加上了synchronized关键
字,在new的过程中,整个都处在set中,所以不会被其他线程的get打断,取到
不完整的引用。

  原子性针对一个long或者double或者一个引用类型,对于引用类型,原子性
是指在new实例的过程中,不会被其他线程取到,即不会被其他线程取到一个不
完整的实例。这种原子性可以理解为new的过程处于一个synchronize段的set方
法中,只有set结束才可以被get到,即new的整个过程都是处于set中的。也可以
理解为指令重排序,禁止把new过程的指令与把引用赋值给变量的语句重排序,
赋值只发生在new结束之后。

3.饿汉式变形版,我们知道饿汉式的单例模式是可以保证线程安全之一点的,但是唯一不好的地方就是在还没有调用的时候就初始化了实例,这样以来导致还没有使用就占用过多的空间,那么有没有一种办法可以解决这个问题,让饿汉式也可以实现使用的时候创建实例,下面我们就来实现这一点

public class InnerHolderSingleMode {
    
    private InnerHolderSingleMode(){}
    
    private static class ClassHolder{
        private static InnerHolderSingleMode instance = new InnerHolderSingleMode();
    }

    public static InnerHolderSingleMode getInstance (){
        return ClassHolder.instance;
    }
}

大致相同的写法,区别就在于我们的类的实例是在这个类中内部类中创建的,这样就可以实现当我们调用
getInstance方法的时候再去初始化外部类的实例,达到节省空间的目的,同样也保证了线程安全

4.枚举实现单例模式,有言论说枚举是jdk1.5之后实现单例最好的方式

单元素的枚举类型已经成为实现Singleton的最佳方法。
                                                                                    ---------《Effective Java》

枚举单例是如何被保证的:
首先,在枚举中构造方法为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次

public enum EnumSingleMode {
    
    //EnumSingleMode可以看作一个类,INSTANCE就是这个类的实例,
    //每个枚举实例都是static final类型的,也就是说,编译器编译之后
    //INSTANCE前是public static final,所以每个实例只能被初始化一次
    //那么我们要调用这个枚举中的方法或者实例,
    //只需要EnumSingleMode.INSTANCE.getInstance()
    //或者EnumSingleMode.INSTANCE.instance
    //当我们访问这个枚举实例时会执行构造方法,此时我们的单例被实例化instance = new Teacher();
    //因为enum中的实例被保证只会被实例化一次(INSTANCE只会实例化一次)
    //所以构造方法也只会执行一次,所以可以保证我们的类Teacher也被实例话一次
    INSTANCE;
    
    private Teacher instance;

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

推荐阅读更多精彩内容