设计模式学习笔记:01 单例模式

学习主要参考:http://www.cnblogs.com/zuoxiaolong/p/pattern2.html
在应用中如果有两个或者两个以上的实例会引起错误,又或者换句话说,就是这些类,在整个应用中,同一时刻,有且只能有一种状态。

一般创建方法

    public class Singleton {
                //一个静态的实例
        private static Singleton singleton;
                //私有化构造函数
        private Singleton(){}
                //给出一个公共的静态方法返回一个单一实例
        public static Singleton getInstance(){
                    if (singleton == null) {
                        singleton = new Singleton();
                 }
            return singleton;
        }
    }

但是这样写的在高并发的情况下可能会产生多个实例。
解释:
线程A执行到getInstance方法的时候,在判断完singleton是null的时候,线程A就进入了if块准备创造实例,但是同时另外一个线程B在线程A还未创造出实例之前,就又进行了singleton是否为null的判断,这时singleton依然为null,所以线程B也会创造一个实例。

标准的 同步

  public class SynchronizedSingleton {
      //一个静态的实例
      private static SynchronizedSingleton synchronizedSingleton;
      //私有化构造函数
      private SynchronizedSingleton(){}
      //给出一个公共的静态方法返回一个单一实例
      public static SynchronizedSingleton getInstance(){
          if (synchronizedSingleton == null) {
              synchronized (SynchronizedSingleton.class) {
                  if (synchronizedSingleton == null) {
                      synchronizedSingleton = new SynchronizedSingleton();
                  }
              }
          }
          return synchronizedSingleton;
      }
  }

解释

在同步块中,我们再次判断了synchronizedSingleton是否为null
假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设A线程和B线程都在同步块外面判断了synchronizedSingleton为null,结果A线程首先获得了线程锁,进入了同步块,然后A线程会创造一个实例,此时synchronizedSingleton已经被赋予了实例,A线程退出同步块,直接返回了第一个创造的实例,此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造好了实例,B线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。

大神解释这样依旧存在问题。

JVM中去探索上面这段代码,它就有可能(注意,只是有可能)是有问题的。

因为虚拟机在执行创建实例的这一步操作的时候,其实是分了好几步去进行的,也就是说创建一个新的对象并非是原子性操作。在有些JVM中上述做法是没有问题的,但是有些情况下是会造成莫名的错误。

首先要明白在JVM创建新的对象时,主要要经过三步。

          1.分配内存

          2.初始化构造器

          3.将对象指向分配的内存的地址

这种顺序在上述双重加锁的方式是没有问题的,因为这种情况下JVM是完成了整个对象的构造才将内存的地址交给了对象。但是如果2和3步骤是相反的(2和3可能是相反的是因为JVM会针对字节码进行调优,而其中的一项调优便是调整指令的执行顺序),就会出现问题了。
因为这时将会先将内存地址赋给对象,针对上述的双重加锁,就是说先将分配好的内存地址指给synchronizedSingleton,然后再进行初始化构造器,这时候后面的线程去请求getInstance方法时,会认为synchronizedSingleton对象已经实例化了,直接返回一个引用。如果在初始化构造器之前,这个线程使用了synchronizedSingleton,就会产生莫名的错误。

所以

要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程安全性。比如前面的“饿汉式”实现方式,但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。

如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例,这样一来,只要不使用到这个类级内部类,那就不会创建对象实例。从而同时实现延迟加载和线程安全。

看看代码示例可能会更清晰,示例代码如下:

  public class Singleton {  
      /**
       * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
       * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
       */  
            private static class SingletonHolder {  
                /**
                 * 静态初始化器,由JVM来保证线程安全
                 */  
                private static Singleton instance = new Singleton();  
          }  
            /**
             * 私有化构造方法
             */  
            private Singleton() {  

            }  

            public static  Singleton getInstance() {  
                return SingletonHolder.instance;  
            }  
  }

你可能感兴趣的:(设计模式学习笔记:01 单例模式)