描述
保证一个类只有一个实例,并且提供一个全局访问点
场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池
实现
1. 懒汉模式
- 延迟加载的方式 只有在真正使用的时候,才开始实例化
- 线程安全问题
- double check 加锁优化
- 编译器(JIT) cpu有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字,对于volatile修饰的字段,可以防止指令重排
java
class LazySingleton{
private volatile static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance(){
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();// 1.分配空间 2.初始化 3.引用赋值
}
}
}
return instance;
}
}
备注:
javap -v XXX.class可以看class文件的字节码
2. 饿汉模式
- 类加载的初始化阶段就完成了实例的初始化,本质上是基于JVM类加载机制,保证实例的唯一性
- 类加载的过程:
- 加载二进制数据到内存中,生成对应的class数据结构
- 连接:验证、准备(给类的静态成员变量赋默认值)、解析
- 初始化:给类的静态变量赋值
注意: - 只有在真正使用对应的类时,才会触发初始化
java
class HungrySingleton{
private static final long serialVersionUID = 4416608876659526091L;
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
3. 静态内部类
- 本质上是利用类的加载机制保证线程安全
- 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
java
class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
4. 反射攻击实例
java
public class HungrySingletonTest {
public static void main(String[] args) throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton instance1 = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(instance1);
// // 反射获取实例
Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
HungrySingleton instance2 = declaredConstructor.newInstance();
System.out.println(instance2);
}
}
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
if (instance != null) {
throw new RuntimeException("单例不允许创建多个实例!");
}
}
public static HungrySingleton getInstance(){
return instance;
}
}
5. 枚举
枚举类型支持反序列化的操作 并且不能用反射攻击
其他类型支持反序列化操作案例
java
public class HungrySingletonTest {
public static void main(String[] args) throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton instance1 = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(instance1);
// // 反射获取实例
// Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
// declaredConstructor.setAccessible(true);
// HungrySingleton instance2 = declaredConstructor.newInstance();
// System.out.println(instance2);
// 序列化
HungrySingleton instance2 = HungrySingleton.getInstance();
// ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("testSerializable"));
// oss.writeObject(instance2);
// oss.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
HungrySingleton o = (HungrySingleton) ois.readObject();
ois.close();
System.out.println(o == instance2);
}
}
class HungrySingleton implements Serializable{
private static final long serialVersionUID = 4416608876659526091L;
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
if (instance != null) {
throw new RuntimeException("单例不允许创建多个实例!");
}
}
public static HungrySingleton getInstance(){
return instance;
}
public Object readResolve() throws ObjectStreamException {
return getInstance();
}
}