单例设计模式[Singleton]

单例设计模式[Singleton]

知识点:

1:模式定义、应用场景,类图分析

2:字节码知识/字节码指令重排

3:类加载机制

4:jvm序列化机制

5:单例模式在spring框架和jdk源码中的应用

一:模式定义、应用场景,类图分析

  • 模式定义:保证只有一个实例,并且提供一个全局访问点。
  • 应用场景:重量级对象,不需要多个实例,比如线程池,数据库连接池
  • 类图如下:

  • 单例模式的实现:

    • 1:懒汉模式 :延迟加载,只有在真正使用的时候,才开始实例化

      1) 线程安全问题

      2)double 加锁优化

      1. 编译器(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){}

原文地址:https://www.cnblogs.com/zhoujun007/p/13366465.html