单例设计模式

懒汉式(线程不安全)

(一)
public class Singleton {

    private static Singleton instance = null;

    private Singleton() { }
   
    public static Singleton getInstance() {
    
        if (instance == null) { // 1
            instance = new Singleton(); // 2
        }

        return instance;
    }

}

分析:

       1、当线程A进入到第10行(#1)时,检查instance是否为空,此时是空的。

       2、此时切换到线程B执行。线程B也进入到10行(#1)。同样检查instance为空,于是往下执行11行(#2),

创建了一个实例并返回。

       3、再切换回线程A,由于之前检查到instance为空。所以也会执行11行(#2)创建实例并返回。

       4、至此,两个实例被创建并返回,这不被希望。 

解决线程安全:

       使用synchronized关键字

(二)synchronized 升级
public class Singleton {

    private static Singleton instance = null;

    private Singleton() { }
   
    public static synchronized Singleton getInstance() {
    
        if (instance == null) { // 1
            instance = new Singleton(); // 2
        }

        return instance;
    }

}

分析:

      在(一)中只有当instance还未被实例化时会产生"创建多个实例"问题;而当instance已经实例化完成后,

每次调用getInstance(),其实都是直接返回,即使是多个线程访问,也不会出问题。

      在(二)中加上synchronized后,所有getInstance()的调用都必须要经过同步。其实只是在第一次调用时

要同步,所以此时会产生性能问题

解决性能问题:

(三)性能 升级
public class Singleton {

    private static Singleton instance = null;

    private Singleton() { }
   
    public static Singleton getInstance() {

        if (instance == null) { // 1

            synchronized (Singleton.class) {
                instance = new Singleton(); // 2
            }
        }
        return instance;
    } 

}

分析:

       1、线程A和线程B同时进入第10行(#1)时,这时instance实例为空。

       2、线程A进入synchronized块,创建实例,线程B等待。

       3、线程A返回,线程B继续进入synchronized块,创建实例后返回。

       4、这时又存在两个实例,问题与(一)相同。 

继续解决性能问题(DCL双重检查加锁):

(四)性能 继续升级
public class Singleton {

    private static Singleton instance = null;

    private Singleton() { }
   
    public static Singleton getInstance() {

        if (instance == null) { // 1

            synchronized (Singleton.class) {

                if (instance == null) {
                    instance = new Singleton(); // 2
                }
                
            }
        }
        return instance;
    } 

}

 继续分析:

        当线程A返回,线程B进入synchronized块后,会先检查一下instance实例是否被创建,这时实例已经被线程A

创建,所以线程B不会再创建实例,而是直接返回。

        ————————————

        注意:Java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。该机制导致该DCL方案失效。

                  Singleton instance = new Singleton(); 这行其实做了两个事情:

                   (1)、调用构造方法,创建了一个实例。

                   (2)、把这个实例赋值给instance这个实例引用。

                  在 “无序写”机制中,对于这两步JVM并不保证顺序。所以可能在调用构造方法前,instance已被设

                  置为非空。                

                  [ Java 伪代码 ] 

                  mem = allocate();   (1) // 为Singleton对象分配内存。    

                  instance = mem;    (2) // 此时instance非空,但是还没被初始化。      

                  ctorSingleton(instance);   (3) // 调用Singleton的构造函数,传递instance为实例引用赋值。

                 由此可见当一个线程执行到(2) 时instance已为非空,如果此时另一个线程进入程序判断instance为

                 非空,那么直接就跳转到return instance;而此时Singleton的构造方法还未调用instance,现在的值为

                 allocate()返回的内存对象。所以第二个线程得到的不是Singleton的一个对象实例,而是一个内存对象。

        ————————————

       1、线程A进入getInstance()方法。

       2、因为此时instance为空,所以线程A进入synchronized块。

       3、线程A执行 instance = new Singleton(); 把实例变量instance在调用构造方法之前置为非空。

       4、线程A退出,线程B进入。

       5、线程B检查instance是否为空,此时不为空(在第3步已被线程A置为非空)。线程B返回instance的引用。

          (此时instance的引用指向的并不是Singleton实例,因为没有调用构造方法。

       6、线程B退出,线程A进入。

       7、线程A继续调用构造方法,完成instance的初始化,再返回。

 继续解决DCL双重检查:

(五)DCL 升级
public class Singleton {

    private static Singleton instance = null;

    private Singleton() { }
   
    public static Singleton getInstance() {

        if (instance == null) { // 1

            synchronized (Singleton.class) {

                Singleton temp = instance; // 2
                if (temp == null) {
                    synchronized (Singleton.class) { // 3
                        temp = new Singleton(); // 4
                    }
                    instance = temp; // 5
                }
                
            }
        }
        return instance;
    } 

}

继续分析:

       1、线程A进入getInstance()方法。

       2、因为instance是空,所以线程A进入位置#1的第一个synchronized块。

       3、线程A执行位置#2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。

       4、因为temp为空,所以线程A进入位置#3的第二个synchronized块。

       5、线程A执行位置#4的代码,把temp设置成非空,但还没有调用构造方法!("无序写"机制)

       6、切换为线程B进入getInstance()方法。

       7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于锁已被线程A占用,

             所以无法进入,线程B阻塞。

       8、线程A激活,继续执行位置#4的代码。调用构造方法,生成实例。

       9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。

      10、线程B激活,进入第一个synchronized块。

      11、线程B执行位置#2的代码,把instance实例赋值给temp本地变量。

      12、线程B判断本地变量temp不为空,所以跳过if判断。返回instance实例。

      由此来看现在代码量比之前增加很多,很是麻烦!!!

继续优化:  饿汉式(预先初始化static成员变量) 

(六)升级为 饿汉式
public class Singleton {

    private static Singleton instance = new Singleton ();

    private Singleton() { }
   
    public static Singleton getInstance() {

        return instance;
    }

}

继续优化分析:

       由于Java的机制,static的成员变量只在类加载的时候初始化一次,且类加载线程安全,但是牺牲懒加载,

创建完实例若不使用,耗费内存资源。

终极方案:

(七)内部类 终极方案
public class Singleton {

     private static class SingletonHolder{

         private static Singleton instance = new Singleton();
     }

     private SingletonFive() { }

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

}

终极分析:

       由于Java机制,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了Lazy),

而且其加载过程线程安全(实现线程安全)。内部类加载的时候实例化一次instance。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值