前段时间需要两次面试,都需要手撕一个单例模式,还问了double check机制,当事心里一万个草拟马奔腾而过…,到现在为止,博主连懒汉和恶汉都分不清,所以含泪写下该篇博文。
-
单例模式(Singletom)
- 单例对象static关键字修饰。
- 私有化构造器。
- 保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。
-
理解定义后,就会手撕饿汉和懒汉的代码了。
-
饿汉单例模式
- 顾名思义,饿者,穷也!不给准备好食物,怕被饿死,所有需要提前准备好食物。
- 即,单例不是在第一次使用的时候创建,而是在类加载的时候进行创建。
- 即,在类加载时候已经创建完毕,保证多线程环境线程安全。
// 实现方法一 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; } }
-
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;
}
}
-
手撕完饿汉和懒汉,我们可以继续刚双重检查机制了
-
其实双重检查机制,是针对懒汉单例模式进行的优化,为什么?因为懒汉有钱。
-
要了解该机制之前,前文中已经知道懒汉在多线程环境下,会发生对象不一致的问题,那么我们怎么解决了。
-
方案一,简单加入互斥锁进行强化。即,在静态返回方法上加锁。这样加入synchonized来修饰,在性能开销上有点大,所以一般不推荐该方案。
//通过静态方法返回单例 public synchronized static SingleLanHan getInsance() { if(instance == null) { instance = new SingleLanHan(); } return instance; }
-
方案二,双重检查机制+synchronized = 双重同步锁单例模式
-
千呼万唤始出来,讲道理双重锁面试的时候,一般都是问下概念,手写的很少。稍好的面试官,会步步引导你写出来,反正博主直接就被问double check,呵呵。
-
那么,为什么要用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; } }
-
如果有任何错误,请联系和指正。
-
-
-