• 单例设计模式-Double Check


    单例设计模式-Double Check

    单例设计模式主要是为了保证只创建一个对象,其余时候需要复用的话就直接引用那个对象即可。简单来说,就是在整个应用中保证只有一个类的实例存在。

    我们常用的单例模式有  饿汉式单例 和 饱汉式单例

    饿汉式单例设计模式

    package com.imodule.dataImport.study;
    /**
     * 饿汉式单例设计模式
     * 1.定义一个静态私有成员变量 并初始值为当前类的实例化对象
     * 2.私有化构造方法(防止被外部通过 new 构造方法创建对象)
     * 3.定义public的静态方法返回当前类的实例化对象
     * @author S0111
     *
     */
    public class SingleTon1 {
    
    	private static final SingleTon1 instance = new SingleTon1();
    	    
    	private SingleTon1() {}
    	    
    	public static SingleTon1 getInstance() {
    	     return instance;
    	}
    }
    

      优点:实现起来简单,没有多线程同步问题。

      缺点:当类Singletont被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。

    饱汉式单例设计模式-初版(适合单线程访问)

    package com.imodule.dataImport.study;
    /**
     * 饱汉-单例设计模式
     * 1.定义一个静态的私有成员变量,初始值为null
     * 2.定义私有的构造方法(防止被外部通过 new 构造方法创建对象)
     * 3.定义一个静态的public获取对象的方法,里卖先判断对象是否为空
     * 		是: 通过构造方法创建对象再返回
     * 		否: 直接返回对象
     * @author S0111
     *
     */
    public class SingleTon {
    
    	private static SingleTon instance = null;
    	private SingleTon(){}
    	
    	public static SingleTon getInstance(){
    		if(instance == null){
    			instance = new SingleTon();
    		}
    		return instance;
    	}
    }

    优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。

    缺点:在多线程环境中,这种实现方法会有问题,不能完全保证单例的状态。

    在多线程访问的情况下以上单例是可能会失效的,因为创建对象分为三步

    eg: instance = new SingleTon();

    1.为单例对象在堆上分配内存 //mem = allocate();  

    2.创建SingleTon的实例对象  // ctorSingleton(instance);

    3.将实例对象 instance 指向内存空间 (这一步instance才为非null) // instance = mem; 

    Java编译为字节码后,JVM 的即时编译器中存在指令重排序的优化 ,有可能进行指令重排序 ,最终的执行顺序可能是 1-2-3 也可能是 1-3-2(2与3互换顺序)

    1.为单例对象在堆上分配内存 //mem = allocate();  

    3.将实例对象 instance 指向内存空间 (这一步instance才为非null) // instance = mem; 

    2.创建SingleTon的实例对象  // ctorSingleton(instance);

    如果指令的执行顺序如上 1-3-2,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance(此时的instance为null),然后被调用使用,从而会报错。

    这里的关键在于 —— 线程thread1对instance的写操作没有完成,线程thread2就执行了读操作

    饱汉式单例 多线程访问-方案一(在获取实例的方法上加锁 synchronized)

    public class SingleTon {
    
    	private static SingleTon instance = null;
    	private SingleTon(){}
    	
    	//直接加锁 但是这样子效率有点低
    	public static synchronized SingleTon getInstance(){
    		if(instance == null){
    			instance = new SingleTon();
    		}
    		return instance;
    	}
    }  

    不推荐使用,效率太低了啦

    饱汉式单例 多线程访问-方案二 也就是本文的重点啦 (Double-check)

    1.使用volatile关键字修饰 instance,防止指令重排序

    2.加上同步代码块  保证线程安全(比同步方法高效,因为同步方法每次获取对象都会执行,同步代码块只在第一次获取对象时会执行,后期会直接返回对象信息)

    public class SingleTon {
    
    	private static volatile SingleTon instance = null;// volatile 关键字修饰变量 防止指令重排序
    	private SingleTon(){}
    	
    	public static  SingleTon getInstance(){
    		if(instance == null){
    			//同步代码块 只有在第一次获取对象的时候会执行到 ,第二次及以后访问时 instance变量均非null故不会往下执行了 直接返回啦
    			synchronized(SingleTon.class){
    				if(instance == null){
    					instance = new SingleTon();
    				}
    			}
    		}
    		return instance;
    	}
    }
    

     推荐使用!!!

    volatile关键字的两层语义

      一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

      1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

      2)禁止进行指令重排序。

     

  • 相关阅读:
    nyoj118 修路工程 次小生成树
    nyoj99 单词连接 欧拉回路
    NYOJ289 苹果 典型背包
    nyoj 139 牌数 康拓展开
    poj1423 NYOJ_69 数字长度 斯特林公式 对数应用
    NYOJ311 完全背包 对照苹果
    sort 函数的应用
    NYOJ120 校园网络 强连接
    nyoj219 计算日期 吉姆拉森公式
    把SmartQ5系统装在SD卡上
  • 原文地址:https://www.cnblogs.com/DFX339/p/12531008.html
Copyright © 2020-2023  润新知