设计模式(1)——单例模式Java实现

概念

单例模式——确保一个类只有一个实例,并且提供一个全局访问的方法。

  • 为什么要使用单例模式
    我认为学习设计模式的意义在于应用,而不是为了学习而学习,为了面试而死记硬背,因为这样终究会遗忘的。
    单例模式的根本是保证在运行的系统中一个类只有一个实例,其他方法或者类都是使用这唯一的实例。生产中最常见的就是资源的管理,例如,只有一个正在打印的任务,只有一个资源的管理器,只有一个订单的ID生成器等等,当然我们在设计系统时可以采用分布式的方式代替单例,不过这样就会引入分布式的解决方案,在小系统中我们更加倾向于使用单例模式。

单例模式的实现要点

  1. 定义static的单例对象实例的引用
  2. 定义static的访问方法
  3. 私有的构造函数
单例模式类图

单例模式的Java实现

单例模式的实现方式有很多种,也有各种各样的命名,看上去很复杂,其实抛开教科书一样的理论名称,捋顺思路实现起来很简单的。

首先,在类加载的时候直接初始化,即简单又能保证线程安全,只是因为在不使用的时候初始化可能带来性能上消耗。

public class Singleton {
    //直接初始化,每次获取实例时都返回已经初始化好的实例。
    private static Singleton instance = new Singleton();
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return instance;
    }
}

如果初始化对系统性能有影响,则可以选择在使用时初始化(延迟初始化)

很明显下面这是一个错误的延迟初始化,在校验instance是否为null 的时候,可能多条线程同时看到null的状态,并且都进行了初始化;也可能某条线程看到非null的状态,而实际上返回的确是一个未初始化完成的实例。多线程问题很难通过结果来推断程序执行期间到底发生了什么,所以定位问题就显得极为困难,尽量在设计编写代码时保证多线程的安全性。

//这是一个错误的延迟初始化
public class Singleton {
    private static Singleton instance;

    private Singleton(){}

    public static Singleton getInstance(){
        //线程不安全
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

既然上面会有线程不安全问题,那么在初始化的时候,我们就应该进行同步,避免多线程同时访问instance实例。

下面的代码在第一次判断为null的时候,对Singleton.class进行加锁同步后进行第二次判断,如果仍旧为null,则进行初始化。

很不幸这段代码也是错误的。这是初学者最容易犯错的,也是大家最迷惑的地方,因为看上去代码是正确的,当多线程同时执行到synchronized代码块时,只有一条线程能够获得锁并且进行初始化,在没有初始化完成前,其他的现在处于等待之中,知道获得锁的线程初始化完成。

问题出现在第一次判断null语句,因为其并不在synchronized代码块中,与第二次判断null的语句不具有先行发生原则Happens-Before,Java虚拟机可能发生重排序操作。简而言之,多线程中可能有线程看到了非null的实例,而实际上得到的是未初始化完成的对象实例,并且将其进行使用,这样后果是不可预知的。

public class Singleton {
    private static Singleton instance;

    private Singleton(){}

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

解决上面问题的办法是volatile修饰instance。因为在先行发生原则Happens-Before中有一条volatile原则,即对volatile变量的写入操作必须在对该变量的读操作之前执行。Java虚拟机的重排序是在对执行结果的不影响的前提下进行的,有了volatile的修饰,即使虚拟机认为读写顺序对结果不影响,但要满足先行发生原则也不会进行重新排序。这样就可以保证线程看到的是一个初始化完整的实例了。

public class Singleton {
    private static volatile Singleton instance;

    private Singleton(){}

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

上面就是双重检验加锁的正确实现了,在早些时候加锁对程序性能的损害是很大的,双重检验加锁虽然实现复杂,却是一个很具有性能优势的实现技巧。被很多面试官不断的考察,去验证面试者的水平,加上volatile和synchronized也是Java中很重要的知识点,所以双重检验加锁成了一个热门的技术考察点。

不过现在随着虚拟机技术的不断提升,同步带来的性能损害逐渐降低,人们没有必要编写双重检验加锁这么复杂的单例模式,而直接使用简单的同步策略,在性能上没有影响,而且更容易理解。

synchronized直接修饰getInstance()方法,保证多线性同步获取instance实例。

//推荐的实现方式
public class Singleton {
    private static Singleton instance;

    private Singleton(){}

    public synchronized static Singleton getInstance(){
        //线程安全
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

总结

单例模式是最简单的,也是最常用的设计模式,考试和面试会被高频率考察到。记住本文的单例模式的实现要点,并且跟随单例模式的Java实现章节的思路,一定会对单例模式有一个透彻的理解。

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