老王讲设计模式(三)——单例模式

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。

早晨,老王来到公司,发现小蔡正对着电脑屏幕发呆,于是走到小蔡身后,发现小蔡正对着一个宝宝照发呆。

《老王讲设计模式(三)——单例模式》

老王拍了一下小蔡肩膀,问:“这个是谁啊?没听说你有男朋友啊。难道是私生子?”

小蔡回头,呸了老王一下,说:“这是我二表姑的侄子的姐夫的姑姑的妹妹的舅舅的儿媳妇的堂哥的娃娃。”

《老王讲设计模式(三)——单例模式》

老王还没回过神来,小蔡又说:“宝宝真可爱,要是我以后要宝宝了,我一定只要一个,我要带她到处去耍,给他买各种漂亮的衣服,各种好玩的玩具,各种吧嗒吧嗒吧嗒……(此处省略数千字)”

老王心想:“都说一个女人等于500只鸭子,这个小蔡何止500只鸭子,简直就是5000只鸭子,而且全是话痨型鸭子。”

《老王讲设计模式(三)——单例模式》

老王趁小蔡两句话之间喘气的时机,赶紧打断了小蔡:“你说到只要一个宝宝,我给你讲讲程序代码只要一个宝宝的方法,好不?”

小蔡一听,很好奇:“程序代码,也有一个宝宝的说法?”

老王说眼看将话题转移了,赶紧接着说:“是啊,那就是单例模式,它可以让整个程序系统里,只存在唯一的实例对象。从而大幅节省内存资源。”

老王不给小蔡搭话的机会,紧接着演示起了代码:“单例模式,分为懒汉式和饿汉式,我们先来看看懒汉式。”

//懒汉式
public class Lazybones {

  //实例对象
  private static Lazybones instances = null;

  //私有的构造函数,阻止实例化对象
  private Lazybones(){}

  //如果发现没有实例对象,就构造一个
  //如果有实例对象,直接返回
  public static Lazybones getInstances(){
    if(instances == null){
      instances = new Lazybones();
    }
    return instances;
  }
}

《老王讲设计模式(三)——单例模式》

小蔡很好奇,问:“代码也有懒汉?”

老王说:“对啊,在使用到的时候才实例化,这就叫懒汉式。这种方式有个好处就是在不使用的时候不会占用内存空间。但是这里也有一个问题。”

小蔡问:“什么问题啊?”

老王说:“在大并发,多线程的环境下,假如有多个线程同时执行到getInstances()方法,第一个线程执行if语句,还没有完成构造时,第二个线程也执行到if这里,这时候instance依然为空。这样线程一和线程二会同时产生两个实例。所以懒汉式的单例模式,并不是线程安全的 。”

小蔡又问:“难道饿汉式是线程安全的?”

老王说:“对的,饿汉式单例模式的确是线程安全的。咱们来看代码。”

//饿汉式
public class Hungry {
  
  //不管三七二十一,直接实例化一个对象
  private static Hungry instances = new Hungry();

  //私有的构造函数,阻止实例化对象
  private Hungry(){}

  //直接返回已经实例化了的对象
  public static Hungry getInstances(){
    return instances;
  }
}

《老王讲设计模式(三)——单例模式》

老王说:“你看,饿汉式单例模式,不管是否使用,直接就将对象实例化在那儿放着,要用的时候直接使用,这样就不用再去判断,所以饿汉式单例模式是线程安全的。”

小蔡问:“老王,我们怎么验证这个这个对象是否单例呢?我们又看不到内存里的分配情况。”

老王说:“这个我们就得靠对象的哈希码了。因为哈希码是用来在散列存储结构中确定对象的存储地址的。简单理解,就是一个对象的hash code和内存地址是一一对应的,只要hash code值相同,那么就是同一内存地址。在Java里,一个对象的toString()方法默认返回这个对象的hash code。我们来看代码。”

public class SingleCheck {

  public static void main(String[] args) {
    //通过饿汉式单例模式,获得3个对象
    Hungry h1 = Hungry.getInstances();
    Hungry h2 = Hungry.getInstances();
    Hungry h3 = Hungry.getInstances();
    //打印其Hash code
    System.out.println("h1:" + h1);
    System.out.println("h2:" + h2);
    System.out.println("h3:" + h3);

    //通过懒汉式单例模式,获得3个对象
    Lazybones l1 = Lazybones.getInstances();
    Lazybones l2 = Lazybones.getInstances();
    Lazybones l3 = Lazybones.getInstances();
    //打印其Hash code
    System.out.println("l1:" + l1);
    System.out.println("l2:" + l2);
    System.out.println("l3:" + l3);
  }
}

其结果为:

《老王讲设计模式(三)——单例模式》

老王说:“从结果,我们可以看到,通过getInstances()方法得到的对象,无论获取多少次,的确为同一对象。这里我再说一下单例模式的关键点:1. 私有的构造函数,防止从外部构造对象。2. 静态的私有变量存储对象。3.共开的获取对象的方法。通过这三点,就可以完成单例模式的编写,通过运用单例模式,我们可以避免重复构造对象,从而节省系统的内存资源消耗。”

小蔡说:“老王,这就是你说的只有一个宝宝的代码?”

老王说:“对啊。正是。”

小蔡眨了眨眼,又问:“那如果我不止想要一个宝宝呢?现在国家政策放开了,可以要两个宝宝了。”

《老王讲设计模式(三)——单例模式》

老王说:“那你就得先找老公了呀。”

《老王讲设计模式(三)——单例模式》

小蔡怒道:“我问的是代码!只要2个宝宝的代码!”

《老王讲设计模式(三)——单例模式》

老王笑道:“那就是多例模式,我们下回再讲。”

更多内容,正在赶来,敬请关注“小蔡和老王的技术日常”。
PS:小蔡和老王的技术日常,已经建立QQ群,QQ群号:261434596,欢迎加入。

《老王讲设计模式(三)——单例模式》

    原文作者:设计模式
    原文地址: https://segmentfault.com/a/1190000007965936
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞