zoukankan      html  css  js  c++  java
  • 单例模式

    单例模式

    1.单例模式

    • 单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
      • 让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
    • 单例模式的普通实现
    package cn.sun.code.twentyone.putong;
    
    /**
     * 单例模式的普通实现
     */
    public class Singleton {
    	
    	private static Singleton instance;
    
    	// 构造方法让其private,这样就堵死了外界利用new创建此类实例的可能
    	private Singleton() {
    		
    	}
    
    	// 此方法是获得本类实例的唯一全局访问点
    	public static Singleton getInstance() {
    		if (instance == null) {
    			instance = new Singleton();
    		}
    
    		return instance;
    	}
    	
    }
    
    
    • 单例模式的好处
      • 保证唯一实例
      • 对唯一实例的受控访问:它可以严格地控制客户怎样访问它以及何时访问它。
        • 怎样:只能通过类内部维护的static方法访问

    2.单例模式的实现

    2.1 懒汉式实现

    • 1中的实现方式即为懒汉式单例
      • 易于分析,该实现方式存在多线程安全问题。一般来说,多线程的安全问题都是由于多个线程执行同一个代码块时,在判断条件处当前线程丧失CPU执行权,由于线程的切换绕过了判断条件从而导致数据错误。懒汉式单例即为最典型,简单的一类多线程错误。
      • 因为线程不安全,所以基本不使用

    2.2 饿汉式单例实现

    • 饿汉式单例实现代码
    package cn.sun.code.twentyone.ehan;
    
    /**
     * 饿汉式单例
     */
    public class EagerSingleton {
    
    	private static EagerSingleton singleton = new EagerSingleton();
    
    	private EagerSingleton() {
    
    	}
    
    	public static EagerSingleton getInstance() {
    		return singleton;
    	}
    }
    
    
    • 该种实现方式是不会存在线程安全问题的:
      • 虚拟机采用了CAS配上失败重试的方式保证更新更新操作的原子性和TLAB两种方式来解决这个问题
    • 这种方式有个明显的缺陷:一旦我访问了Singleton的任何其他的静态域,就会造成实例的初始化,而事实是可能我们从始至终就没有使用这个实例,造成内存的浪费。
    • 不过在有些时候,直接初始化单例的实例也无伤大雅,对项目几乎没什么影响,比如我们在应用启动时就需要加载的配置文件等,就可以采取这种方式去保证单例。

    2.3 双重检查加锁

    • 双重检查加锁代码实现
    package cn.sun.code.twentyone.shuangchongjiancha;
    
    /**
     * 双重检查加锁单例实现
     */
    public class DoubleCheckLockSingleton {
    
    	private static DoubleCheckLockSingleton instance;
    
    	private DoubleCheckLockSingleton() {
    
    	}
    
      // JDK1.6之后对synchronized性能优化了不少
      // 不可避免地还是存在一些性能的问题
    	public static DoubleCheckLockSingleton getInstance() {
    		if (instance == null) {
    			synchronized (DoubleCheckLockSingleton.class) {
    				if (instance == null)
    					instance = new DoubleCheckLockSingleton();
    			}
    		}
    		return instance;
    	}
    	
    }
    
    
    • 第一个判断条件是为了避免每次都判断锁导致效率低下
    • 第二个判断条件是单例的常规操作
      • 同步代码块的存在保证不管CPU执行权如何切换,一个线程都能完整的将代码块中的代码执行完。例如线程A执行到17行时丧失执行权,但是DoubleCheckLockSingleton的锁未被释放,其他线程即使获得CPU执行权也无法进入同步代码块,等到线程A再次获得执行权后将代码块中的代码执行完毕,在内存中创建一个DoubleCheckLockSingleton实例后,将锁释放。其他线程再进入同步代码块中,判断条件if (instance == null)为false,无法再重复创建实例。
      • 此种方式可以可以解决if (instance == null)处因当前执行CPU的线程切换导致的线程安全问题。
    • 指令重排的问题:
      • CPU执行的时候回转换成JVM指令执行
        1. 内存分配给这个对象
        2. 初始化对象
        3. 将初始化好的对象和内存地址建立关联,赋值

    2.4 静态内部类实现单例模式

    package cn.sun.pattern.single;
    
    /**
     * @author : ssk
     * @date : 2020/1/13
     */
    public class LazyInnerClassSingleton {
    
      private LazyInnerClassSingleton(){}
    
      public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.lazy;
      }
    
      // LazyHolder里面的逻辑需要等到外部方法调用时才执行
      // 巧妙利用了内部类的特性
      // JVM底层底层逻辑,完美避免了线程安全问题
      private static class LazyHolder {
    
        private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
    
      }
    
    }
    
    
    • 这种方式不能防止反射暴力创建对象:(同时上述双检锁方式也能通过反射的方式新建实例)
    package cn.sun.pattern.single;
    
    import java.lang.reflect.Constructor;
    
    /**
     * @author : ssk
     * @date : 2020/1/13
     */
    public class SingletonTest {
    
      public static void main(String[] args) {
    //    Class<?> singletonClass = Singleton.class;
        Class<?> singletonClass = LazyInnerClassSingleton.class;
    
        try {
    
          Constructor<?> constructor = singletonClass.getDeclaredConstructor(null);
          constructor.setAccessible(true);
    
          Object singleton = constructor.newInstance();
    
          Object singleton1 = Singleton.getSingleton();
    
          System.out.println(singleton==singleton1);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    
    }
    
    
    output:
    false
    
    Process finished with exit code 0
    
    • 防止反射创建
    package cn.sun.pattern.single;
    
    /**
     * @author : ssk
     * @date : 2020/1/13
     */
    public class LazyInnerClassSingleton {
    
      private LazyInnerClassSingleton(){
        // ================
        if (LazyHolder.lazy != null) {
          throw new RuntimeException("对象已存在多个实例");
        }
        // ================ 
      }
    
      public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.lazy;
      }
    
      // LazyHolder里面的逻辑需要等到外部方法调用时才执行
      // 巧妙利用了内部类的特性
      // JVM底层底层逻辑,完美避免了线程安全问题
      private static class LazyHolder {
    
        private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
    
      }
    
    }
    
    

    2.5 注册式单例(Effective Java)

    • 枚举式单例
    package cn.sun.pattern.single;
    
    /**
     * @author : ssk
     * @date : 2020/1/14
     */
    public enum  EnumSingleton {
    
      INSTANCE;
    
      private Object data;
    
      public Object getData() {
        return data;
      }
      
      public static EnumSingleton getInstance(){
        return INSTANCE;
      }
    }
    
    
    • 这种方式可以防止反射和序列化创建的方式
    • 反射说明
        @CallerSensitive
        public T newInstance(Object ... initargs)
            throws InstantiationException, IllegalAccessException,
                   IllegalArgumentException, InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, null, modifiers);
                }
            }
                     
            // --------从JDK层面保证了枚举不被反射破坏
            if ((clazz.getModifiers() & Modifier.ENUM) != 0)
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
            ConstructorAccessor ca = constructorAccessor;   // read volatile
            if (ca == null) {
                ca = acquireConstructorAccessor();
            }
            @SuppressWarnings("unchecked")
            T inst = (T) ca.newInstance(initargs);
            return inst;
        }
    
    • 枚举类型不能通过反射创建实例

    • 序列化说明

        /**
         * Reads in and returns enum constant, or null if enum type is
         * unresolvable.  Sets passHandle to enum constant's assigned handle.
         */
        private Enum<?> readEnum(boolean unshared) throws IOException {
            if (bin.readByte() != TC_ENUM) {
                throw new InternalError();
            }
    
            ObjectStreamClass desc = readClassDesc(false);
            if (!desc.isEnum()) {
                throw new InvalidClassException("non-enum class: " + desc);
            }
    
            int enumHandle = handles.assign(unshared ? unsharedMarker : null);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(enumHandle, resolveEx);
            }
    
            String name = readString(false);
            Enum<?> result = null;
            Class<?> cl = desc.forClass();
            if (cl != null) {
                try {
                    @SuppressWarnings("unchecked")
                  // -------------
                  // --------------
                    Enum<?> en = Enum.valueOf((Class)cl, name);
                    result = en;
                } catch (IllegalArgumentException ex) {
                    throw (IOException) new InvalidObjectException(
                        "enum constant " + name + " does not exist in " +
                        cl).initCause(ex);
                }
                if (!unshared) {
                    handles.setObject(enumHandle, result);
                }
            }
    
            handles.finish(enumHandle);
            passHandle = enumHandle;
            return result;
        }
    
    • 通过Class对象+枚举量的方式获取枚举实例,Class对象在堆内存中仅有一个,相当于从容器中获取Enum实例。

    2.6 容器式单例

    package cn.sun.pattern.single;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @author : ssk
     * @date : 2020/1/14
     */
    public class ContainerSingleton {
    
      private ContainerSingleton() {
      }
    
      private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
    
      public static Object getBean(String beanName) {
    
        synchronized (ioc) {
          if (!ioc.containsKey(beanName)) {
            Object obj = null;
            try {
              obj = Class.forName(beanName).newInstance();
              ioc.put(beanName, obj);
            } catch (Exception e) {
              e.printStackTrace();
            }
            return obj;
          }
        }
    
        return ioc.get(beanName);
      }
    
    }
    
    

    3.单例模式的应用

    • 单例模式在Java中的应用

      • Runtime是一个典型的例子,看下JDK API对于这个类的解释
      /**
       * Every Java application has a single instance of class
       * <code>Runtime</code> that allows the application to interface with
       * the environment in which the application is running. The current
       * runtime can be obtained from the <code>getRuntime</code> method.
       * <p>
       * An application cannot create its own instance of this class.
       *
       * @author  unascribed
       * @see     java.lang.Runtime#getRuntime()
       * @since   JDK1.0
       */
      
      每个Java应用程序都有一个单独的Runtime类实例,该实例允许应用与该应用所运行的环境沟通。当前运行时环境可以通过访问getRuntime方法获得。
      一个应用不能创建它自己的Runtime类实例。
      
      • 这段话有两个点比较重要
        • 每个Java应用程序都有一个单独的Runtime类实例
        • 一个应用不能创建它自己的Runtime类实例以及当前运行时环境可以通过访问getRuntime方法获得。
      • Runtime代码如下,可以看到是典型的饿汉式单例实现
      public class Runtime {
          private static Runtime currentRuntime = new Runtime();
      
          /**
           * Returns the runtime object associated with the current Java application.
           * Most of the methods of class <code>Runtime</code> are instance
           * methods and must be invoked with respect to the current runtime object.
           *
           * @return  the <code>Runtime</code> object associated with the current
           *          Java application.
           */
          public static Runtime getRuntime() {
              return currentRuntime;
          }
      
          /** Don't let anyone else instantiate this class */
          private Runtime() {}
        
      }
      
  • 相关阅读:
    解决在macOS下安装了python却没有pip命令的问题【经验总结】
    Mac OS下安装MongoDB以及配置方法总结【笔记】
    web上常见的攻击方式及简单的防御方法
    Destoon二开必看执行流程
    网站入侵注入的几种方法总结【笔记】
    命令检测站点是否使用CDN加速
    织梦xss通杀所有版本漏洞【学习笔记】
    让你知晓内容安全的边界:盘点2017、2018这两年的内容监管
    知物由学 | AI网络安全实战:生成对抗网络
    人工智能热门图书(深度学习、TensorFlow)免费送!
  • 原文地址:https://www.cnblogs.com/riders/p/12193349.html
Copyright © 2011-2022 走看看