单例模式应该是最常见的设计模式了
定义
定义:单例对象的类必须保证只有一个实例存在。
为了实现一个健壮的单例,我们应该思考需要做哪些事情?
- 不能随意的让用户new出对象,所以构造函数应该是私有的
- 既然不能直接new,就应该有一个方法专门用来返回实例对象
- 不能clone
- 不能被反序列化
- 多线程使用时,如何保证线程安装
Java的实现方式
根据上面的思考,我们可以一步步的来实现单例模式。
懒汉式
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
使用的时候才进行初始化,但此种写法是线程不安全的。那么是否把getInstance方法加一个锁就可以了呢?
线程安全的懒汉式
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
嗯,这种写法确实安全了,可是效率低,因为有更好的方式啊
饿汉式
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
public class Singleton { private static Singleton instance = null; static { instance = new Singleton(); } private Singleton (){} public static Singleton getInstance() { return instance; } }
|
为什么叫饿汉呢?因为在类加载的时候对象就被创建了啊!这也是该方式不太好的地方,需要的实例应该要在需要用到的时候才初始化呢!
所以应该想想怎样才能延迟加载呢?
静态内部类实现方式
1 2 3 4 5 6 7 8 9
| public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。完美!!
枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public enum Singleton { INSTANCE; public void whoAmI() { System.out.println(this.toString()); } }
public class Singleton { private Singleton(){ } public void whoAmI() { System.out.println(this.toString()); } public static enum SingletonEnum { SINGLETON; private Singleton instance = null; private SingletonEnum(){ instance = new Singleton(); } public Singleton getInstance(){ return instance; } }
}
|
Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次
双重检查锁模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Singleton { private Singleton(){} private volatile static Singleton instance = null;
public static Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) instance = new Singleton(); } } return instance; }
}
|
为什么要这样写呢?
其实这是懒汉式的升级版。懒汉式中所有线程在访问getInstance都会锁住,但实际情况中,很多时候只是读操作,
我们没有必要让每个线程都锁住才调用它。
为什么要加volatile呢?
因为JVM指令可重排,具体可以看看volatile这篇文档
为什么要进行两次非空判断呢?
第一次校验: 也就是第一个if(singleton==null),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。
第二次校验: 也就是第二个if(singleton==null),这个校验是防止二次创建实例,假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singleton==null,此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getInstance方法。
同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。
此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
AtomicReference实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package com.shjlone.singleton;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceSingleton {
private static final AtomicReference<Object> instance;
private AtomicReferenceSingleton() { }
static { instance = new AtomicReference<>(); }
public static AtomicReferenceSingleton getInstance() { Object value = AtomicReferenceSingleton.instance.get(); if (value == null) { synchronized (AtomicReferenceSingleton.instance) { value = AtomicReferenceSingleton.instance.get(); if (value == null) { final AtomicReferenceSingleton actualValue = new AtomicReferenceSingleton(); value = ((actualValue == null)) ? AtomicReferenceSingleton.instance : actualValue; AtomicReferenceSingleton.instance.set(value); } } } return (AtomicReferenceSingleton)((value == AtomicReferenceSingleton.instance) ? null : value); }
public static void main(String[] args) { System.out.println(AtomicReferenceSingleton.getInstance()); System.out.println(AtomicReferenceSingleton.getInstance()); System.out.println(AtomicReferenceSingleton.getInstance());
for(int i=0; i<10; i++) { new Thread() { @Override public void run() { super.run(); System.out.println(AtomicReferenceSingleton.getInstance()); } }.start(); }
} }
|
相较于synchronize,CAS的实现性能更高。
Kotlin的实现方式
由于kotlin的语言特性,有些写法还是有别于Java的。
饿汉式
对!就是这么简单。以上代码转换成Java后,就是在静态代码块中初始化了Singleton。
懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Singleton private constructor() { companion object { private var instance: Singleton? = null get() { if (field == null) { field = Singleton() } return field }
fun get(): Singleton { return instance!! } } }
|
线程安全的懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton private constructor() {
companion object { private var instance: Singleton? = null get() { if (field == null) field = Singleton() return field }
@Synchronized fun instance(): Singleton { return instance!! } } }
|
双重锁校验
1 2 3 4 5
| class Singleton private constructor() { companion object { val instance by lazy { Singleton() } } }
|
静态内部类的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton private constructor() { companion object {
@JvmStatic fun getInstance() { return Holder.instance }
} private object Holder { val instance = Singleton() }
}
|
1 2 3 4
| enum class Singleton { INSTANCE; }
|
如何处理反序列化
最开始的分析指出,为了不让别人直接new,构造函数需要设置成私有的。除此之外,还可能被反序列化。那么怎么解决这个问题呢?
1 2 3 4 5 6 7
| public class Singleton implements Serializable { private final static Singleton instance; private Object readResolve() { return instance; } }
|
这样就行了?可以参考https://www.jianshu.com/p/ea1d9bc40341这篇文章了解原理。
如何防止反射
可以在私有构造函数中加一些限制条件,比如判断静态变量是否已经存在了
参考