设计模式之浅谈单例模式

前段时间需要两次面试,都需要手撕一个单例模式,还问了double check机制,当事心里一万个草拟马奔腾而过…,到现在为止,博主连懒汉和恶汉都分不清,所以含泪写下该篇博文。

  • 单例模式(Singletom)
    1. 单例对象static关键字修饰。
    2. 私有化构造器。
    3. 保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。
  • 理解定义后,就会手撕饿汉和懒汉的代码了。
    1. 饿汉单例模式
      • 顾名思义,饿者,穷也!不给准备好食物,怕被饿死,所有需要提前准备好食物。
      • 即,单例不是在第一次使用的时候创建,而是在类加载的时候进行创建。
      • 即,在类加载时候已经创建完毕,保证多线程环境线程安全。
    // 实现方法一
    public class SingleEHan {
    	//构造私有化
    	private SingleEHan() {
    	}
    	//单例对象
    	public static SingleEHan instance = new SingleEHan();
    	//通过静态方法返回单例
    	public static SingleEHan getInsance() {
    		return instance;
    	}
    }
    // 实现方法二
    public class SingleEHanSecond {
    	// 构造私有化
    	private SingleEHanSecond() {
    	}
    	public static SingleEHanSecond instance = null;
    	static {
    		instance = new SingleEHanSecond(); 
    	}
    	// 单例对象
    	// 通过静态方法返回单例
    	public static SingleEHanSecond getInsance() {
    		return instance;
    	}
    }
    
    1. 懒(饱)汉单例模式
      • 顾明思议,懒(饱)者,就是有钱,土豪一个,用的时候new一个。
      • 即,使用时创建,不慌不忙。
      • 当然,懒汉在多线程环境是不安全的,可能会返回两个不同的对象。其原因,详见代码块。
public class Singleton_Test {
	// static关键字修饰
	private static Singleton_Test singleton;
	// 私有构造器
	private Singleton_Test() {
	}
	// 提供静态方法, 返回单例
	public static Singleton_Test getInstance() {
		if(singleton == null) {
            // 这里是发生返回不同对象的现场。
            // 假设线程A和B,即A进入后在该行进行挂起,那么线程B在进入的时候,发现对象为null
            // 则B随即创建对象返回,这时候A继续执行,同样创建一个对象返回,最终会造成两个对象
            // 不一致的现象。
			return new Singleton_Test();
		}
		return singleton;
	}
}
  • 手撕完饿汉和懒汉,我们可以继续刚双重检查机制了
    1. 其实双重检查机制,是针对懒汉单例模式进行的优化,为什么?因为懒汉有钱。

      • 要了解该机制之前,前文中已经知道懒汉在多线程环境下,会发生对象不一致的问题,那么我们怎么解决了。

      • 方案一,简单加入互斥锁进行强化。即,在静态返回方法上加锁。这样加入synchonized来修饰,在性能开销上有点大,所以一般不推荐该方案。

        //通过静态方法返回单例
        public synchronized static SingleLanHan getInsance() {
            if(instance == null) {
                instance = new SingleLanHan();
            }
            return instance;
        }
        
      • 方案二,双重检查机制+synchronized = 双重同步锁单例模式

        1. 千呼万唤始出来,讲道理双重锁面试的时候,一般都是问下概念,手写的很少。稍好的面试官,会步步引导你写出来,反正博主直接就被问double check,呵呵。

        2. 那么,为什么要用double check来保证多线程单例的安全?

          • 方案一中线程不安全的原因,其实是在CPU层面,即在执行到instance = new SingleLanHan(),该语句的时候,发生了指令重排。

             instance = new SingleLanHan();
             // 发生指令重排
            
          • 那么发生了指令重排,我们就可以使用vilatile关键来修改,禁止指令重排。

            public class SingleLanHanPlus {
            	
            	//构造私有化
            	private SingleLanHanPlus() {
            	}
            	/*
            	 * 线程不安全原因:CPU层面
            	 * 执行instance = new SingleLanHanPlus(), 为操作
            	 * 1,分配内存空间
            	 * 2, 初始化对象
            	 * 3, 设置instance指向刚分配的内存
            	 * 单线程环境, 指令重排对以上3步无影响
            	 * 多线程环境:
            	 * 	JVM和CPU优化, 发生指令重排变成 1, 3, 2
            	 * 解决方案 : 使用volatile修饰实例对象, 禁止指令重排序
            	 */
            	//单例对象
            	public volatile  static SingleLanHanPlus instance = null;
            	//通过静态方法返回单例
            	public static SingleLanHanPlus getInsance() { 
            		if(instance == null) {
            			synchronized(SingleLanHanPlus.class) {
            				//线程发现instance不为null, 直接返回
            				if(instance == null){
            					instance = new SingleLanHanPlus();
            				}
            			}
            		}
            		return instance;
            	}
            }
            

        如果有任何错误,请联系和指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值