zoukankan      html  css  js  c++  java
  • 单例设计模式[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){}
    
    
  • 相关阅读:
    编译内核开始的小问题Unable to find the Ncurses libraries
    qq for linux Ubuntu 64位兼容
    ubuntu下安装lighttpd
    搭建boa服务器
    INI file and Iniparser
    kernel常用.gitignore配置
    光谱学习
    jump to case label fpermissive
    Qt通用方法及类库5
    Qt通用方法及类库1
  • 原文地址:https://www.cnblogs.com/zhoujun007/p/13366465.html
Copyright © 2011-2022 走看看