单例设计模式[Singleton]
知识点:
1:模式定义、应用场景,类图分析
2:字节码知识/字节码指令重排
3:类加载机制
4:jvm序列化机制
5:单例模式在spring框架和jdk源码中的应用
一:模式定义、应用场景,类图分析
- 模式定义:保证只有一个实例,并且提供一个全局访问点。
- 应用场景:重量级对象,不需要多个实例,比如线程池,数据库连接池
- 类图如下:
-
单例模式的实现:
-
1:懒汉模式 :延迟加载,只有在真正使用的时候,才开始实例化
1) 线程安全问题
2)double 加锁优化
- 编译器(JIT),cpu有可能对执行进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,对于volatile修饰的字段,可以防止指令重排
test1:单例模式的实现,存在线程安全问题,如果在多线程的情况下,会有问题
package Singleton; public class LazySingletonTest { public static void main(String[] args) { LazySingleton instance1 = LazySingleton.getInstance(); LazySingleton instance2 = LazySingleton.getInstance(); System.out.println(instance1); //Singleton.LazySingleton@1b6d3586 System.out.println(instance2); //Singleton.LazySingleton@1b6d3586 } } /** * 定义一个懒汉式的单例类 */ class LazySingleton{ //定义一个静态属性 private static LazySingleton instance; //私有的构造函数 private LazySingleton() { } public static LazySingleton getInstance(){ //对应的实例为空,才实例化 if (instance == null) { instance = new LazySingleton(); } return instance; } }
test2:
package Singleton; public class LazySingletonTest { public static void main(String[] args) { new Thread( ()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); //Singleton.LazySingleton@4f0c4707 }).start(); new Thread( ()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); //Singleton.LazySingleton@7e129604 }).start(); } } class LazySingleton{ //定义一个静态属性 private static LazySingleton instance; //私有的构造函数 private LazySingleton() { } public static LazySingleton getInstance() { //对应的实例为空,才实例化 if (instance == null) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; } }
解决多线程安全问题,加锁。--double 加锁优化
//对应的实例为空,才实例化 if (instance == null) { synchronized (LazySingleton.class){ if(instance ==null){ instance = new LazySingleton(); } } }
test3:---只记录
//对应的实例为空,才实例化 if (instance == null) { synchronized (LazySingleton.class){ if(instance ==null){ instance = new LazySingleton(); //字节码层 //JIT ,CPU //1 分配空间 2 初始化 3 引用赋值 //单线程 2和3顺序没关系,多线程可能会造成异常 //解决办法 volatile关键字 } } } //解决办法 volatile关键字 private volatile static LazySingleton instance;
-
2 :饿汉模式:
类加载的 初始化阶段就完成了 实例的初始化。本质上就是借助jvm类加载机制,保证实例的唯一性。
类加载过程:
1:加载二进制数据到内存中,生成对应的class数据结构
2:连接 a .验证 b.准备(给类的静态成员变量赋默认值) c.解析
3:初始化: 给类的静态变量赋初值
只有在真正使用对应的类的时候,才会触发初始化 如(当前类时启动类即main函数所在的类,直接new操作,访问静态属性,访问静态方法,用反射访问类,初始化一个子类等等)
package Singleton; public class HungrySingletonTest { public static void main(String[] args) { HungrySingleton instance1 = HungrySingleton.getInstance(); HungrySingleton instance2 = HungrySingleton.getInstance(); System.out.println(instance1==instance2); //true } } class HungrySingleton { private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return instance; } }
-
3:静态内部类方式
1)本质上是利用类的加载机制来保证线程安全
2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
package Singleton; public class InnerClassSingletonTest { public static void main(String[] args) { //test1: InnerClassSingleton instance1 = InnerClassSingleton.getInstance(); InnerClassSingleton instance2 = InnerClassSingleton.getInstance(); System.out.println(instance1==instance2); //true //test2:测试线程安全 new Thread(() -> { InnerClassSingleton instance= InnerClassSingleton.getInstance(); System.out.println(instance); //Singleton.InnerClassSingleton@7e129604 }).start(); new Thread(() -> { InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(instance); //Singleton.InnerClassSingleton@7e129604 }).start(); } } class InnerClassSingleton{ //静态内部内,本质也是jav的类加载机制 private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } //私有构造方法 private InnerClassSingleton(){} //公共的获取实例的方法 public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
- 4:反射攻击实例:--饿汉模式和内部类可以进行反射攻击
package test; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * @author noob * @created 2020-07 23 13:35 */ public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //反射拿到实例 Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance(); //获取实例 InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton==instance); //fasle } } class InnerClassSingleton{ //静态内部内,本质也是jav的类加载机制 private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } //私有构造方法 private InnerClassSingleton(){ //解决反射攻击 if(InnerClassHolder.instance != null){ throw new RuntimeException("单例不允许多个实例"); } } //公共的获取实例的方法 public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
-
5:枚举类型
1)天然不支持反射创建对应的实例,且有自己的反序列化机制
2:利用类加载机制保证线程安全
package Singleton; public enum EnmuSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) { EnmuSingleton instance = EnmuSingleton.INSTANCE; EnmuSingleton instance1 = EnmuSingleton.INSTANCE; System.out.println(instance == instance1); //true } }
-
6 序列化
1)可以利用 指定方法 来替换从反序列化流中的数据 如下
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
package test; import java.io.*; public class InnerClassSingletonTest { public static void main(String[] args) throws IOException, ClassNotFoundException { InnerClassSingleton instance = InnerClassSingleton.getInstance(); // 序列化实例 // InnerClassSingleton instance = InnerClassSingleton.getInstance(); // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable")); // oos.writeObject(instance); // oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable")); Object obj = (InnerClassSingleton)ois.readObject(); System.out.println(instance == obj); //false,单例拿到的对象和序列化拿到的对象不是同一个 } static class InnerClassSingleton implements Serializable { // static final long serialVersionUID = 42L; //静态内部内,本质也是jav的类加载机制 private static class InnerClassHolder { private static InnerClassSingleton instance = new InnerClassSingleton(); } //私有构造方法 private InnerClassSingleton() { if (InnerClassHolder.instance != null) { throw new RuntimeException("单例不允许多个实例"); } } //公共的获取实例的方法 public static InnerClassSingleton getInstance() { return InnerClassHolder.instance; } //Object readResolve() throws ObjectStreamException{ // return InnerClassHolder.instance; //} } }
解决办法:
Classes that need to designate a replacement when an instance of it * is read from the stream should implement this special method with the * exact signature. * * <PRE> * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; * </PRE><p> 添加版本号和实现方法 static final long serialVersionUID = 42L; Object readResolve() throws ObjectStreamException{ return InnerClassHolder.instance; }
-
jdk源码中的应用
Idea:ctrl+N 搜索Runtime
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
标准的饿汉形式的单例模式
Idea:ctrl+N 搜索 DefaultSingletonBeanRegistry
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
protected void addSingleton(String beanName, Object singletonObject) {}
protected Object getSingleton(String beanName, boolean allowEarlyReference){}