要点:
- 单例类构造函数是private的;
- 区分类加载时创建单例,还是实际使用时(延迟分配);
- 可以加锁,但要考虑效率,判null可以只在单例为null时存在锁,其他时候无锁获取单例;
- 反序列化可能弄出多个单例,目前枚举类型避免了该问题,其实质是自己要重写readObject或readResolve(在readObject之后调用,都是用来覆盖默认反序列化方法的)方法。
- 反射也能打破单例。避免方法:修改构造函数,设置一个标记,使new只能执行一次。第二次就抛异常。
一、饿汉模式
要点:静态实例声明时初始化;
优点:简单;
缺点:实例在类加载时创建,而不是使用时。
public class EagerInstance {
private EagerInstance() {}
private static EagerInstance instance = new EagerInstance();
public static EagerInstance getInstance() {
return instance;
}
}
二、延迟分配 + 同步方法
要点:获取单例的方法加上synchronized关键字;
优点:简单;
缺点:每次获取单例需要竞争锁。
三、延迟分配 + 双检查锁
要点:
实例声明时,一定要加上volatile,否则其他线程可能访问到未初始化的单例对象;
获取单例的方法中,实例是否为空要检查两次,第二次在synchronized中;
synchronized对当前类加锁,而不是实例对象;
instanceRef优化:除了第一次需要初始化外,其他情况下都不需要初始化。考虑已初始化时,在使用instanceRef之前,instance需要读两次(一次判断是否为null、一次return),使用instanceRef后,instance只需要读一次。据说使该方法有25%的提升。
可参考: https://en.wikipedia.org/wiki/Double-checked_locking
优点:延迟分配,同时避免每次加锁;
缺点:
复杂;
volatile在1.5后才增强了语义,避免了本例中new重排;即1.5前,该方法是不可靠的。
public class DoubleCheckedLocking {
private DoubleCheckedLocking(){
}
private static volatile DoubleCheckedLocking instance = null;
public static DoubleCheckedLocking getInstance() {
DoubleCheckedLocking instanceRef = instance;
if (instanceRef == null) {
synchronized(DoubleCheckedLocking.class){
if (instance == null) {
instance = instanceRef = new DoubleCheckedLocking();
}
}
}
return instanceRef;
}
}
四、利用类初始化锁
要点:
类初始化时(即类加载并生成class对象时),会加锁,是线程安全的;
利用内部静态类;
优点:延迟分配 + 不需要手动加锁;
缺点:复杂,多了个静态内部类;
补充:可以利用类初始化锁,构造一个死锁的例子,见后面的代码
public class InstanceHolder {
private InstanceHolder() {
}
private static class Holder {
public static InstanceHolder instance = new InstanceHolder();
}
public static InstanceHolder getInstance() {
return Holder.instance;
}
}
死锁:
public class DeadLock {
static volatile boolean flag = false;
static {
new Thread(() -> {
flag = true;
}).start();
while(!flag);
}
}
五、枚举单例
要点:
枚举量其实是public static 修饰的,会在类加载时的static{ }语句中完成初始化;
枚举的构造函数时private的;
重写了readObject(直接抛异常:InvalidObjectException("can't deserialize enum"))。
enum Singleton {
INSTANCE;
private Instance instance;
Singleton () {
instance = new Instance();
}
public Instance getInstance() {
return instance;
}
}
public class EnumTest {
public static void main(String[] args) {
// 单例,序列化安全
Singleton.INSTANCE.getInstance();
}
}
六、反序列化 + 反射安全
要点:
修改构造函数,置一个状态量,如果已初始化,则抛异常;
重写readResolve方法。
真的安全吗?
通过反射还是可以设置flag,从而产生多个实例。
也就是说反射下,没有绝对的单例了。
究其原因:你都把class文件给别人了,别人想怎么搞都行。
class SafeSingle implements Serializable{
private static final long serialVersionUID = 1L;
private static boolean flag = false;
private SafeSingle(){
synchronized (SafeSingle.class) {
if (!flag) {
flag = true;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
}
private static volatile SafeSingle single;
public static SafeSingle getInstance(){
SafeSingle singleRef = single;
if ( singleRef == null ) {
synchronized (SafeSingle.class) {
if ( single == null ) {
single = singleRef = new SafeSingle();
}
}
}
return singleRef;
}
private Object readResolve() {
return single;
}
}