大话设计模式——单例模式

在开发过程中,其实很多情况下,都需要用到单例模式来维持对象的唯一性。
比如线程池、数据源、sessionFactory等。

一般的做法(懒汉式):

public class MyClass{
  //饿汉式是直接声明变量是就初始化:
  //private static MyClass myClass=new MyClass();
  private static MyClass myClass;
  //将构造器声明为私有的,不允许外部类创建实例对象
  private MyClass(){}

  //声明一个静态方法来返或一个单例对象
  public static MyClass getInstance(){
    if(myClass == null)
       myClass = new MyClass();
     return myClass;
  }

}

但是这个会在多线程下回导致数据不一致性。
大话设计模式——单例模式_第1张图片
这样线程一跟线程二就获得不同的实例对象,打破了唯一性。

这个问题就变为多线程问题,一般通过加锁是可以解决问题的,比如:

public class MyClass{
  private static MyClass myClass;
  //将构造器声明为私有的,不允许外部类创建实例对象
  private MyClass(){}

  //声明一个静态方法来返或一个单例对象       
  //通过增加synchronized来保证同一时刻只有一个线程可以执行getInstance()函数
  public static synchronized MyClass getInstance(){
    if(myClass == null)
       myClass = new MyClass();
     return myClass;
  }
}

但是这种加锁方法会带来性能问题,因为其实对于单例模式,只有在第一次初始化myClass的时候需要控制只有一个线程可以进行实例化对象,对于之后的获得对象,由于已经实例化了,是可以直接返回的,但是由于synchronized的声明就降低了性能。

这样就引出了“双重检查加锁”概念,代码如下:

public class MyClass{
  //增加volatile关键字来修饰"实例变量"
  private volatile static MyClass myClass;
  //将构造器声明为私有的,不允许外部类创建实例对象
  private MyClass(){}

  //声明一个静态方法来返或一个单例对象       
  //通过增加synchronized来保证同一时刻只有一个线程可以执行getInstance()函数
  //将锁的粒度降低
  public static MyClass getInstance(){
    if(myClass == null){
        synchronized(this.class){
        if(myClass == null){
          myClass = new MyClass();
          }
     }
      return myClass;
  }
}

这样就保证了只有当第一次需要实例化对象的时候才会用到synchronized控制并发。

其实看到这,很多人会有一个疑问,好像没必要用到volatile关键字啊。
我第一次看见也是这么觉得,后来查看了一下资料,发现了“新大陆”。
主要原因就是编译器为了一下优化操作,会执行指令重排。具体原因可以参考博客:双重检查锁定原理详解
当然单例模式还有很多不同的实现方式,比如枚举等。

以上内容为《大话设计模式》的学习笔记

你可能感兴趣的:(设计模式)