设计模式之单例模式

各个模式均有自己的出现的理由以及应用场景,相较于普通代码有一定的优势的时候的,设计模式便也应运而生了。今天所要记录的单例模式,它的优势在哪呢?

单例模式简历

1、出生原因

当我们在一个管理者管辖范围不明确,各个部门间的领导互相插手彼此部门间的事物,员工一会被这个boss分配任务,一会被那个boss分配任务的公司里,作为一个普通员工,您怎么想?对于一个管理层这样混乱与模糊的公司,您怎么看?在一个项目中也一样,我们的某些类,一会被这个实例化,一会被那个实例化,而这个类的功能却是很单一的管理着同样一个领域,这个时候,你会不会想着做点什么来结束这个混乱的现象呢?重构、优化一下吧,特定的领域管理交由一个固定的对象来管理,那么,这个时候,单例模式就出现了。

2、定义

单例对象的类必须保证只有一个实例存在,通过阻止外部实例化和修改,来控制所创建的对象的数量。

3、适用情况

整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

4、实现思路

一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

5、亮点

整个系统中只有这么一个实例对象,方便各处的运用,也方便管理该对象所要管理的东西。

实例讲解

接下来,我们通过三个不同层次的单例模式实例,来较为深入的理解在java代码中如何实现及其原理。

首先,在我们的level1包下面创建Singleton类,

public class Singleton {
  private static Singleton instance  = null;
   public static Singleton getInstance(){
       if(instance == null){
          instance = new Singleton();
      }
       return instance;
   }
    private Singleton() {}
    public void doSomething(){
      System.out.println("'how are you'");
  }
}

将Singleton的构造函数私有化,阻止外部实例化,再通过调用一个共有的接口getInstance()方法,首先if(instance == null)判断其是否创建,再返回instance对象。这一切看起来都还挺不错的,一环接一环的保证了咱们Singleton类只返回一个实例化对象instance。可是真的是这样吗?让我们的大脑跑起A、B双线程来,假如当A准备创建instance的时候B正好进行if()判断语句,这个时候instance == null 是要返回true的,那么,这种情况肯定就会创建两个instance对象了,所有,这个实现方法在多线程里,是不安全的单例模式,或者说多线程情况下,这种实现不合理。

那么,开动我们的大脑,想想办法来控制他的实例化判断。于是,会写出如下代码:

 public class Singleton {
   private static Singleton instance = null;
  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class) {                    if (instance == null) {
        instance = new Singleton();
                 }
        }
      }
      return instance;
  }
    private Singleton() {
  }
  public void doSomething() {
      System.out.println("'how are you'");
  }
}

我们通过double-checked-locking的方法来阻止其多线程运行。当getInstance()方法被调用的时候,首先进行null值检测,若是false直接返回instance,否则,运行同步锁,(synchronized,它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码),然后再进行null值检测,返回instance。这样,我们的程序就没有多线程情况下被多次创建的可能。感觉,程序写到这里,已经是很酷了,其实我也觉得,哈哈。不过,当我们了解创建一个变量的步骤时,可能就会有疑问了。

创建一个变量,有以下几个步骤:申请一块内存,调用构造方法进行初始化,分配一个指针指向这块内存。而这些都是JVM来执行,但是JVM仅仅是一个标准,没有规定编译器优化的内容,导致其可以自由进行优化,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。那么,就有可能会出现这种情况:当A线程开始创建Singleton实例时,B线程调用getInstance()方法检测null时,假如此时A已经将instance指向了那块内存,但还没有赋值,那么B线程检测null时会直接返回instance,但是instance的构造并没有完成,程序便出错了。

这可让我们很是懊恼,怎么感觉处处碰壁的样子,难道JAVA就不能安全的实现单例模式吗?结果肯定是有的,平下心来,我们来看看接下来的实现:

public class Singleton {
   private Singleton() {
   }
     private static class SingletonInstance {
       private static final Singleton instance = new Singleton();
   }
     public static Singleton getInstance() {
      return SingletonInstance.instance;
  }   
  public void doSomething() {
      System.out.println("'how are you'");
  }
}

使用java静态内部类,JVM能够保证当一个类被加载时,这个加载过程是互斥的。调用getInstance()方法时,首先加载SingletonInstance类,这个类有一个static实例,所以需要调用Singleton的构造方法,最后getInstance将这个instance返回给使用者。

总结

通过学习单例模式之后,感觉学习一门语言,仅仅浅显的学习是不够的,有时候,你仅仅知道怎么用,你可能永远都写不出好的程序,这不是危言耸听。据我所知,优秀的程序员,几乎都学习过编译原理,当然,信息来源于知乎,因为自己平时知乎浏览的要多一些。所以,编译原理,已经被我提上今后的日程表了。

设计模式这一块,可能得暂时停下来了,因为有了新的任务,不过剩下的一篇代理模式我会抽空更新的,这是我的承诺@杜珊

加油吧,大家~!!

Share Comments