饿汉式
public class HungrySingleton {
private static final HungrySingleton INSTANCE =new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
- 要点
- 私有构造函数
- 变量为private static final类型,static变量在类一加载时就初始化,并且一个类就只有一个,final关键字防止变量被覆盖。
- public static类型的getInstance方法
- 缺点
1.没有用到也初始化,浪费内存
2.有反射攻击和反序列化攻击风险
懒汉式
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance ==null) {
synchronized (LazySingleton.class) {
if (instance ==null) {
instance =new LazySingleton();
}
}
}
return instance;
}
}
- 要点
1.私有构造函数
2.private static volatile变量,volatile阻止指令重排序,防止造成返回未完全初始化的对象的问题
3.public static类型的getInstance方法
4.双重检查锁
- 缺点
1.使用了Synchronized,影响性能
2.有反射攻击和反序列化攻击风险
静态内部类
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton (){}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.INSTANCE;
}
private static class InnerClass {
private static final StaticInnerClassSingleton INSTANCE =new StaticInnerClassSingleton();
}
}
- 要点
1.私有构造函数
2.public static类型的getInstance方法
3.静态内部私有类,必须是静态类否则没有办法声明static类型成员
4.结合了饿汉和懒汉两种写法,内部类采用饿汉式,外部类利用了内部类延迟加载的特性做成懒汉式
- 缺点
有反射攻击和反序列化攻击风险
枚举
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
- 原理
将枚举类型生成的.class文件反序列化,发现编译器为枚举类型生成了一个继承Enum类的class,包括一个私有构造函数和static类型的变量INSTANCE
public final class EnumSingleton extends Enum {
private EnumSingleton(String s, int i) {
super(s, i);
}
public static final EnumSingleton INSTANCE;
static {
INSTANCE = new EnumSingleton("INSTANCE", 0);
}
}
由此可知,枚举单例本质上还是饿汉式,所以它还是具备饿汉式浪费内存的缺点,但优点是可以防止反射攻击和反序列化攻击
总结
- 共同点
1.私有构造函数
2.public static 的getInstance方法返回单例对象
3.public static的单例对象
- 不同点
方法 | 特点 |
---|---|
饿汉式 | 单例对象声明为final,防止创建后被修改。对象在类加载时被初始化 |
懒汉式 | 采用双重检查锁,为了防止返回不完全初始化对象,成员变量要加volatile关键字 |
静态内部类 | 外部类不声明成员变量,而是在内部类声明,所以内部类必须是静态的,只有静态内部类能声明静态成员变量 |
枚举 | 不是声明一个class而是声明一个enum |