学记-设计模式-单例模式
前言
最近在准备面试,梳理知识的时候发现自己总是边学边忘,很多知识点容易混淆。经过反思发现,我这个死脑筋就是记不住好。所以我决定把他们都记在笔记里,这样既可以加深我印象,又可以和大家一起学习讨论。
说回单例模式,最近看了小傅哥和遇见狂神说的设计模式,有很多有趣的地方。本文是对单例模式的学习笔记。
介绍
单例模式,属于创建型模式,也就是要保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现
单例模式的实现方式比较多,我这边列举7种方式。
1、饿汉式(线程安全)
public class Hungry_0 {
private static Hungry_0 instance = new Hungry_0();
public Hungry_0() {
}
public static Hungry_0 getInstance() {
return instance;
}
}
-
在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可。
-
但是这种方式有个不好的地方就是会提前加载资源到内存中,就好比,你打开的是应用的个人中心,应用却把非个人中心的资源也加载了。
2、懒汉式(线程不安全)
private static Lazy_0 INSTANCE;
private Lazy_0() {}
private static Lazy_0 getInstance() {
if (null == INSTANCE) {
INSTANCE = new Lazy_0();
}
return INSTANCE;
}
- 在多线程情况下不安全,并发问题。
3、懒汉式-方法级锁(线程安全)
private static Lazy_1 INSTANCE;
private Lazy_1() {
}
private synchronized static Lazy_1 getInstance() {
if (null == INSTANCE) {
INSTANCE = new Lazy_1();
}
return INSTANCE;
}
- 虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。如果不是特殊情况下,不建议此种方式实现单例模式。
4、懒汉式-双重锁校验(线程安全)
//volatile 防止指令重排
private volatile static Lazy_2 INSTANCE;
private Lazy_2() {
}
private static Lazy_2 getInstance() {
if (null == INSTANCE) {
synchronized (Lazy_2.class){
if (null == INSTANCE) {
INSTANCE = new Lazy_2();
}
}
}
return INSTANCE;
}
-
双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。
-
但是在特定情况下不安全,不过比较少见,可以用来炫技,比如反射。
//尝试使用反射创建一个新的实例
Lazy_2 instance = Lazy_2.getInstance();
Constructor<Lazy_2> constructor = (Constructor<Lazy_2>)instance.getClass().getDeclaredConstructor(null);
constructor.setAccessible(true);
Lazy_2 clone = constructor.newInstance();
System.out.println(instance); //lazy.Lazy_2@39a054a5
System.out.println(clone); //lazy.Lazy_2@71bc1ae4
怎样防止上面的反射创建呢,我们可以在构造方法中加入反射校验
//volatile 防止指令重排
private volatile static Lazy_3 INSTANCE;
private Lazy_3() {
if (null != INSTANCE) {
throw new RuntimeException("Cannot reflectively create use invoke");
}
}
private static Lazy_3 getInstance() {
if (null == INSTANCE) {
synchronized (Lazy_3.class) {
if (null == INSTANCE) {
INSTANCE = new Lazy_3();
}
}
}
return INSTANCE;
}
当然,还有别的方法可以再次创建实例。(道高一尺,魔高一丈)
5、懒汉式-枚举(线程安全)
public enum Lazy_4 {
INSTANCE;
private static Lazy_4 getInstance() {
return INSTANCE;
}
}
-
为什么枚举类是线程安全的呢,通过源码查看发现枚举类也是私有构造器。
尝试使用反射去new一个新的实例
Lazy_4 instance = Lazy_4.getInstance(); System.out.println(instance.hashCode());//lazy.Lazy_4@39a054a5 //尝试使用多次方式创建对象 Constructor<Lazy_4> constructor = Lazy_4.class.getDeclaredConstructor(null); constructor.setAccessible(true); System.out.println(constructor.newInstance());
却提示找不到该构造方法,找不到构造方法??
为什么找不到呢,经过一系列查找,最终通过字节码查看下发现,并没有无参构造方法。
再试试反射,之后出现了“Cannot reflectively create enum objects”,原来是在newInstance的时候会判断该类是否是枚举类,防止其多次创建实例。
6、懒汉式- CAS(线程安全)
private static final AtomicReference<Lazy_5> INSTANCE = new AtomicReference<Lazy_5>();
private static boolean flag = true;
private Lazy_5() {
}
private static final Lazy_5 getInstance() {
for (; ; ) {
Lazy_5 instance = INSTANCE.get();
if (null != instance) return instance;
INSTANCE.compareAndSet(null, new Lazy_5());
return INSTANCE.get();
}
}
- 比较替换的方法,当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。
7、懒汉式- 静态内部类(线程安全)
private static class SingletonHolder {
private static Lazy_6 instance = new Lazy_6();
}
private Lazy_6() {
}
public static Lazy_6 getInstance() {
return SingletonHolder.instance;
}
总结
这也是我第一次把学习笔记记在这里,如果有哪里写得不对的地方,也请大家指出。