设计模式之单例模式(自用)

单例模式,顾名思义,指的是一个类只存在一个实例。

那么,如何保证某一个类只存在一个实例呢?对象的创建是通过类的构造函数来实现的(也可以通过clone方式,但是这种方法前提是首先有一个对象;或者通过反序列化,这种方法同样也需要原本类的对象存在),所以就需要保证类的构造函数只能被调用一次。若是将构造函数的权限设置为public的,这样显然无法满足要求。所以需要将构造方法隐藏起来,而且构造方法只在类还未创建实例的情况下被调用,如果类已经创建了实例,则不再调用。

所以可以将构造函数的访问权限设置为最私密的private,同时暴露一个公共的接口给调用者,而在接口处统一的返回实例给调用者。如此,对于每一个需要对象的调用者,可以通过这个接口保证返回的都是同一个实例。

这样的话,类就需要持有一个自身类的实例,当需要调用者调用公共的接口想要获得这个类的实例的时候,统一的将这个实例返回。

public class Singleton{

private static  Singleton Instance=new Singleton();//持有一个类的实例

private Singleton(){};//私有的构造方法

public static Singleton getInstance(){

    return Singleton.instance; 

    }

}

这种方式实现的单例模式也叫做饿汉式,实例的创建时机是在类加载的时候静态变量初始化的时候。至于为什么叫做饿汉式,可能是因为这种方式是在类加载的时候获得唯一实例的,而不考虑类加载时是否需要这个类的实例(可能只是初次调用这个类的其他类方法就会进行唯一的创建)。

与这种方法不同的另一种实现叫做懒汉式,可能是因为懒汉是要到事情迫在眉睫的才会去做,而懒汉式的单例模式实现这种方式是在需要这个类的实例再去创建这个实例,是一种延迟创建对象的实现方式。

那么,如何实现这种呢?即不是让类加载的时候就进行对象创建,而是等到真正需要这个对象的时候(也就是调用公共的接口想要获得实例的时候),可以想到的是,将创建对象的过程放到接口内,这样就可以保证创建对象的时机是在调用这个公共接口的时候。但是显然仅仅这样做,是没有办法保证实例是唯一的,每一次的调用都将会创建一个新的实例返回。

可以想到的一种办法,在每次调用公共接口的时候,检查是否这个类的实例是否已经创建,这样就需要一个标识,来标识类的实例是否已经被创建,如果被创建了,就直接返回这个类的实例,如果没有创建,就先创建,再返回。所以这种方式,类还是需要持有自身的实例,由于类的实例持有是通过保存这个对象的引用来实现的,所以可以通过判断这个引用是否为null,就可以判断实例是否已经被创建。

public class Singleton{

private static  Singleton Instance=null;

private Singleton(){};//私有的构造方法

public static Singleton getInstance(){

    if(instance==null)

    instance=new Singleton();

    return Singleton.instance;

    }

}

这种就是懒汉式的设计单例模式了,这种方式虽然是实现了延迟创建对象的功能(由于对象的创建是需要相当程度的占用资源,如果大量的对象创建都集中在类加载时,这样是非常不好的),但是这种方式只能保证在单线程下是安全的,也就是在多线程下是有问题的。

原因是因为,由于对象的创建不具有原子性,对象的创建可以简单的分为几个阶段1、在堆中开辟可用的空间 2、对象进行初始化 3、将对象的引用复制给引用变量,也就是“=”这个操作。java虚拟机jvm会对指令进行重排序来优化程序,jvm会保证单线程下重排序不会对结果产生影响,假设重排序可能是先把对象的引用传给引用变量,然后再进行初始化对象,就算是这样,只要保证在调用对象之前,已经完成了初始化,那么重排序将不会影响结果。但是在多线程的情况之下,这种重排序的机制将可能出现问题,假设线程1为第一个调用new Singleton的线程,而且在它创建对象的时候发生了重排序,jvm在未将对象初始化的引用先传递给了引用变量instance,而此时,线程2也在访问getInstance(),那么当它进行instance==null判断的时候,将会认为对象已经创建(实际上对象还未初始化)而直接返回,如果此时再继续进行对象的调用,将会发生错误,因为对象没有初始化完成。而且多个线程最大权限访问同一个资源,也是会出问题的。(线程1进行对象初始化后还未进行引用赋值,线程2此时将判断对象还未创建,从而进入if子句中进行对象创建,这样就无法保证实例唯一)

那么如何解决这个问题呢?首先想到的可能是加锁,既然在对象创建的未完成的时候可能有其他的线程访问这个资源,那么可以将这个getInstance()加上synchronized关键字,每次只允许一个线程访问这个方法,这样确实杜绝了上面所说的问题,但是由于在这么大的粒度下面加上同步,将会非常影响效率。

public class Singleton{

private static  Singleton Instance=null;

private Singleton(){};//私有的构造方法

public static synchronized Singleton getInstance(){

    if(instance==null)

    instance=new Singleton();

    return Singleton.instance;

}

}

因为这种方法效率非常差,所以还需要另外想办法解决多线程下单例模式的问题。所以有人想到一种将同步的粒度缩小的办法,叫做双重校验锁。

public class Singleton{

private static  Singleton Instance=null;

private Singleton(){};//私有的构造方法

public static Singleton getInstance(){

if(instance==null){

    synchronized(Singleton.class){

        if(instance==null){

            instance=new Singleton();

        }

    }

}

return Singleton.instance;

}

}

这种方法解决了上一个方法效率的问题,也解决了只创建唯一的实例,但是由于重排序机制,将可能会导致双重校验锁失效,想到了既然问题是重排序机制下其他线程访问了未初始化完成的对象造成的可能错误。那么可以想办法让jvm不进行重排序,volatile关键字有两种语义,一个是线程间的可见性,保证其标识的值是最新的值,从内存中读取,而不是缓存的值,另一种语义是禁止重排序。所以可以用volatile解决双重校验锁失效的问题。 

public class Singleton{

private  volatile static  Singleton Instance=null;

private Singleton(){};//私有的构造方法

public static Singleton getInstance(){

if(instance==null){

synchronized(Singleton.class){

if(instance==null){

instance=new Singleton();

}

}

}

return Singleton.instance;

}

}

还有一种方法是静态内部类的方法,饿汉式的方式没有多线程下的诸多问题,但是由于其不具有延迟性,而不被采用,但是可以将这种类加载时由类直接创建实例的方式的优点利用,通过静态内部类的方式,这样既保证了延迟创建对象,又多线程安全。

public class Singleton{

    private static class SingletonHolder{

        private static Singleton instance=new Singleton();

    }

private Singleton(){};

public static Singleton getInstance(){

        return SingletonHoder.instance;

    }

}

上面的所有的类内的单例对象引用也可以定义为final。

还有一种是枚举的方式,由于枚举类型的枚举值的对象创建是由java自己实现的,它的构造器是私有的。

public enum Singleton{

INSTANCE;//枚举值,java会在创建class的时候将它定义为static final的,它的值是Singleton类型的对象

同样的,和一般类一样,后面还可以按照需要,定义类和实例的属性或者方法。这些都是可以的。

}

Singleton.INSTANCE就是singleton类型的唯一实例了。

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