1. 说明
目标:单例类只有一个实例。
实现步骤:
- 构造方法私有化;
- 单例类自己创建唯一实例;
- 单例类向外部暴露一个
public static
方法返回唯一实例。
2. 实现方式
2.1 饿汉式
2.1.1 特点
- 懒加载:否
- 线程安全:是
- 实现难度:易
- 优点:无锁,运行效率高;
- 缺点:类加载时就实例化,如果应用程序不使用该类,则造成内存浪费;
- 原理:只有在类加载到内存时,实例化一次。
注:如果确定某个单例类一定会用到,则推荐使用此种方式。
2.1.2 示例
public class Hungry {
/**
* 1. 构造方法私有化
*/
private Hungry() {
}
/**
* 2. 创建唯一实例
*/
private static Hungry instance = new Hungry();
/**
* 暴露方法给外界
* @return 唯一实例
*/
public static Hungry getInstance() {
return instance;
}
}
2.2 懒汉式
2.2.1 特点
- 懒加载:是
- 线程安全:是
- 实现难度:易
- 优点:懒加载,避免内存浪费;
- 缺点:每次获取都有锁,性能低;
- 原理:加
synchronized
锁,多个线程在访问getInstance()
方法时,顺序获取到方法锁,进而保证了并发访问时不会多次执行构造方法产生多个实例。
注:不推荐使用。
其他说明:去掉synchronized
就是线程不安全的情形,也不推荐使用。
2.2.2 示例
public class LazySafe {
private LazySafe() {
}
private static LazySafe instance;
public static synchronized LazySafe getInstance() {
if (instance == null) {
return new LazySafe();
}
return instance;
}
}
2.3 双重校验锁
2.3.1 特点
- 懒加载:是
- 线程安全:是
- 实现难度:复杂
- 优点:懒加载,避免内存浪费;双重校验,高性能;
- 缺点:加
volatile
关键字,并进行synchronized
锁控制,实现复杂;
2.3.2 示例
public class DoubleCheck {
private volatile static DoubleCheck instance;
private DoubleCheck() {
}
public static DoubleCheck getInstance() {
if (instance == null) {
synchronized (DoubleCheck.class) {
if (instance == null) {
instance = new DoubleCheck();
}
}
}
return instance;
}
}
2.4 静态内部类
2.4.1 特点
- 懒加载:是
- 线程安全:是
- 实现难度:一般
- 优点:懒加载,性能高,通过静态内部类方式,与双重检查功效类同;
2.4.2 示例
public class InnerStatic {
private InnerStatic() {
}
public static InnerStatic getInstance() {
return Holder.instance;
}
private static class Holder {
private static final InnerStatic instance = new InnerStatic();
}
}
2.5 枚举方式
2.5.1 特点
- 懒加载:否
- 线程安全:是
- 实现难度:易
- 优点:简洁,自动支持序列化机制,绝对防止多次实例化,也是Effective Java 作者 Josh Bloch 提倡的方式,同时可以拒绝反射攻击而创建多个实例;
- 缺点:非懒加载;
2.5.2 示例
public enum SingletonEnum {
INSTANCE;
// 可以添加任意属性或任意方法
// 通过SingletonEnum.INSTANCE访问实例,或封装统一命名的getInstance()方法返回实例。
public static SingletonEnum getInstance() {
return INSTANCE;
}
}
3. 总结
一般情况,使用饿汉式即可;如有明确需要懒加载,可使用静态内部类方式;如果涉及到反序列化建议用枚举方式;如果有其他特殊要求,可以考虑双重检查锁的方式。
注:不推荐使用懒汉式,它的问题在于将性能损耗延迟到首笔交易过程中,这在频繁迭代而经常重启服务的敏捷时代,是不容易被容忍的,原因就是它将低效传递给了用户,而不是提前备好。
综合考虑,推荐使用枚举类的方式实现单例模式。