设计模式:Singleton(单例模式)

定义:

单例模式是GOF二十三中经典设计模式的简单常用的一种设计模式,单例模式的基本结构需满足以下要求。

  • 单例模式的核心结构只有一个单例类,单例模式要保证这个类在运行期间只能被实例化一次,即只会被创建唯一的一个单例类的实例。
  • 单例模式需要提供一个全局唯一能得到这个类实例的访问点,一般通过定义一个名称类似为GetInstance的公用方法实现这一目的。

实现:

恶汉式单例模式,当JVM加载类时就创建好实例对象。如果该实例一直没被使用,则浪费了系统资源。

public class MySingleTon_1 {
    //在jvm加载类时就创建好对象
    private static  MySingleTon_1 uniqueSingleton = new MySingleTon_1() ;
    //私有化构造函数
    private MySingleTon_1(){};

    public  static MySingleTon_1 getInstanse(){
        return  uniqueSingleton;

    }
}

 

懒汉式单例模式。当用户需要创造一个实例对象的时候才创建。

public class MySingleTon {
    //利用一个静态变量来记录唯一的实例
    private static  MySingleTon uniqueSingleton ;
    //私有化构造函数
    private MySingleTon(){};

    public  static MySingleTon getInstanse(){
        //如果实例对象为null,说明实例对象还没创建,则调用私有构造函数创建实例,并把他赋值给static对象。
        if(uniqueSingleton == null){
            uniqueSingleton = new MySingleTon();
        }
        return  uniqueSingleton;

    }

上面的单例模式在多线程下会出现问题,当多个线程在第一次调用getInstanse方法时,可能会创建多个实例对象。将上述方法 进行改进,在getInstanse上加上synchronized,或者在代码块上加上synchronized

public class MySingleTon_2 {
    //利用一个静态变量来记录唯一的实例
    private static  MySingleTon_2 uniqueSingleton ;
    //私有化构造函数
    private MySingleTon_2(){};
   //将此方法设置为同步的保证多线程下的安全性
    public  synchronized  static MySingleTon_2 getInstanse(){
        //如果实例对象为null,说明实例对象还没创建,则调用私有构造函数创建实例,并把他赋值给static对象。
        if(uniqueSingleton == null){
            uniqueSingleton = new MySingleTon_2();
        }
        return  uniqueSingleton;

    }
public class MySingleTon_2 {
    //利用一个静态变量来记录唯一的实例
    private static  MySingleTon_2 uniqueSingleton ;
    //私有化构造函数
    private MySingleTon_2(){};
   
    public   static MySingleTon_2 getInstanse(){
        //同步代码块
        synchronized(MySingleTon_2.class) {
            if (uniqueSingleton == null) {
                uniqueSingleton = new MySingleTon_2();
            }
        }
        return  uniqueSingleton;

    }
}

 

但是同步会对性能带来很大的问题,只有第一次执行方法时才需要同步,后续的执行并不需要同步。采用双重检查加锁的方法来进行同步。

public class MySingleTon_3{
    //利用一个静态变量来记录唯一的实例 ,volatile 关键字保证uniqueSingleton赋予Mysingle_3实例时,多线程能正确的处理uniqueSingleton
    private volatile  static  MySingleTon_3 uniqueSingleton ;
    //私有化构造函数
    private MySingleTon_3(){};
    //
    public   static MySingleTon_3 getInstanse(){
        //只有第一次会进入里面的判断
        if(uniqueSingleton == null){
            //进入同步代码块,再检查一次,如果没有实例则创建实例
           synchronized (MySingleTon_3.class){
               if(uniqueSingleton == null){
                   uniqueSingleton = new MySingleTon_3(); //1
               }
           }
        }
        return  uniqueSingleton;

    }
}

那么,如果上述的实现没有使用 volatile 修饰 ,会导致什么情形发生呢? 

(1)、当我们写了 new 操作,JVM 到底会发生什么?

  首先,我们要明白的是:new MysingleTon_3()是一个非原子操作。代码行uniqueSingleton = new MysingleTon_3(); 的执行过程可以形象地用如下3行伪代码来表示:

  1. memory = allocate(); //1:分配对象的内存空间

  2. ctorInstance(memory); //2:初始化对象

  3. uniqueSingleton = memory; //3:使uniqueSingleton指向刚分配的内存地址

  •  

  但实际上,这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序导致先执行第3行后执行第2行,也就是说其真实执行顺序可能是下面这种:

  1. memory = allocate(); //1:分配对象的内存空间

  2. uniqueSingleton = memory; //3:使uniqueSingleton指向刚分配的内存地址

  3. ctorInstance(memory); //2:初始化对象


(2)、重排序情景再现 
   
  了解 new 操作是非原子的并且可能发生重排序这一事实后,我们回过头看使用 双重检查加锁 的同步延迟加载的实现:

  我们需要重新考察上述//1处代码。此行代码创建了一个MysingleTon_3 对象并初始化变量 uniqueSingleton 来引用此对象。这行代码存在的问题是,在 Mysingle_3构造函数体执行之前,变量 uniqueSingleton 可能提前成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程将得到的是一个不完整(未初始化)的对象,会导致系统崩溃。下面是程序可能的一组执行步骤:

  1、线程 1 进入 getInstance 方法; 
  2、由于 uniqueSingleton 为 null,线程 1 进入 synchronized 块; 
  3、同样由于 uniqueSingleton 为 null,线程 1 开始执行构造函数,但在构造函数执行之前,使实例成为非 null,并且该实例是未初始化的; 
  4、线程 1 被线程 2 预占; 
  5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 得到一个不完整(未初始化)的 MysingleTon_3 对象; 
  6、线程 2 被线程 1 预占。 
  7、线程 1 通过运行 uniqueSingleton 对象的构造函数来完成对该对象的初始化。

  显然,一旦我们的程序在执行过程中发生了上述情形,就会造成灾难性的后果,而这种安全隐患正是由于指令重排序的问题所导致的。让人兴奋地是,volatile 关键字正好可以完美解决了这个问题。也就是说,我们只需使用volatile关键字修饰单例引用就可以避免上述灾难。

 

还有使用内部类来实现延时加载的单例模式,当内部类被加载时创建实例对象。

public class MySingleTon_4 {
    
        // 私有内部类,按需加载,用时加载,也就是延迟加载
        private static class Holder {
            private static MySingleTon_4 singleton4 = new MySingleTon_4();
        }

        private MySingleTon_4(){

        }

        public static MySingleTon_4 getSingleton5() {
            return Holder.singleton4;
        }

    }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值