• 单例模式以及如何强化单例属性


    特点
    单例类只能有一个实例。
    单例类必须自己创建自己的唯一实例。
    单例类必须给所有其他对象提供这个实例。
    优点
    提供了对唯一实例的受控访问;
    由于系统内存中只存在一个对象,因此可节约系统的资源,对于一些频繁的创建和销毁的对象,单例模式可以提升系统的性能。
    单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例在内存中,避免了对同一个资源文件的重复写操作。
    单例模式可以在系统设置全局的访问点,优化和共享访问,例如:设计一个单例类,负责所有数据表的映射处理。
    缺点
    单例模式一般没有接口,扩展很困难,如果要扩展,则必须修改代码。
    单例类职责过重,在一定的程度上违背了单一职责。
    滥用单例模式会带来一些负面问题。如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
    饿汉式单例类

     1 public class EagerSingleton{
     2     private static EagerSingleton instance = new EagerSingleton();
     3     /**
     4     *私有默认构造
     5     */
     6     private EagerSingleton(){}
     7     //静态工厂方法
     8     public static EagerSingleton getInstance(){
     9         return instance;
    10     }
    11 }

    饿汉式单例模式,会在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,这时候,实例就被创建出来了,除非系统重启,否则这个对象不会改变,所以本身就是线程安全的。
    饿汉式是典型的空间换时间,类加载的时候就创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

    懒汉式单例类

    public class LazySingleton{
        private static LazySingleton instance = null;
        //私有默认构造函数
        private LaySingleton(){}
        //静态工厂方法
        public static synchronized LazySingleton getInstance(){
            if(instance == null){
                instance = new LazySingleton();
            }
            return instance;
         }
    }        

    懒汉式单例类对静态工厂方法使用了同步化,以处理多线程环境,此方式虽然解决了同步问题,但是该方式效率比较低下,下一个线程想要获取对象,就必须等上一个线程释放锁之后,才可以去获取。
    懒汉式会在需要使用实例的时候才创建实例。
    懒汉式是典型的时间换空间,就是每次实例都会判断,看是否需要创建实例,浪费了判断的时间,当然,如果一直没有人使用的话,就不会创建实例,节约了内存空间(但是没用,你建它干嘛)。
    双重检验加锁模式

    public class Sington{
        private volatile static Singleton instance = null;
        private Singleton(){}
    
        public static Singleton getInstance(){
        //先检查实例是否存在,如果不存在才进入下面的同步块
            if(instance == null){
                synchronized(Singleton.class){
                //再次检查实例是否存在,如果不存在再创建实例
                if(instance == null){
                    instance = new Singleton();
                    }
                }
            }
            return instance;
         }
    }        

    这种模式既可以保证线程安全的创建实例,而又不会对性能造成太大的影响。只会在第一次创建的时候同步,以后就都不会同步了,从而加快了运行速度。
    提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行的效率不会很高。

    静态内部类模式

    这种模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
    相应的基础知识:
    ❤ 什么是类级内部类?
      简单来说,类级内部类就是有static修饰的成员内部类,如果没有sattic修饰的成员内部类被称为对象级内部类。
      类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建。而对象级的内部类的实例,是绑定在外部对象实例中的。
      类级内部类中,可以定义静态的方法,在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
      内级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。
    ❤ 多线程缺省同步锁知识
      在多线程开发中,为了解决并发问题,主要是通过加锁的方式来进行同步控制,但是在一些情况中,JVM已经隐含地为你执行了同步,这些情况下就不用自己再来进行同步控制了,这些情况包括:
      1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
      2.访问final字段时
      3.在创建线程之前创建对象时
      4.线程可以看见它将要处理的对象时

    public class InnerSingleton {
        //私有构造方法
        private InnerSingleton(){
            
        }
        /**
         * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
         * 而且只有被调用时才会被装载,从而实现了延迟加载
         */
        private static class InnerInSingleton{
            //静态的初始化器,由jvm来保证线程安全
            private static InnerSingleton singleton = new InnerSingleton();
        }
        //调用实例
        public static InnerSingleton getSingleton(){
            return InnerInSingleton.singleton;
        }
    }

    当getInstance方法第一次被调用的时候,它第一次读取InnerInSingleton.singleton,导致InnerInSingleton类得到初始化;这种方式是Singleton类被装载了,singleton实例不一定被初始化。因为InnerInSingleton类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载InnerInSingleton类,从而实例化singleton。而这个类在装载并初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全。

    这种模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

    以上四种单例模式,并不能严格保证全局只有一个对象。

    可以通过反射机制,设置AccessibleObject.setAccexxible(true),改变构造器的访问属性,调用构造器生成新的实例。
    例1: 以调用内部静态类的单例模式为例

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            InnerSingleton singleton = InnerSingleton.getSingleton();
            
            //getDeclaredConstructor:返回参数类型的所有构造器,包括public的和非public的,当然也包括private的。
            Constructor<InnerSingleton> constructor = InnerSingleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            InnerSingleton singleton2 = constructor.newInstance();
            System.out.println(singleton == singleton2);
        }

    输出:

    false

    表明并不是同一个实例。
    要防止这种情况,可以修改构造器,在第二次创建实例的时候抛出异常。
    例2:还是以内部静态类的单例模式为例:

    public class InnerSingleton {
        //定义count为全局静态变量
        private static int count = 0;
        //私有构造方法
        private InnerSingleton(){
            if(count > 0){
                throw new IllegalArgumentException("不能创造InnerSingleton两次!");
            }
            count++;
        }
        /**
         * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
         * 而且只有被调用时才会被装载,从而实现了延迟加载
         */
        private static class InnerInSingleton{
            //静态的初始化器,由jvm来保证线程安全
            private static InnerSingleton singleton = new InnerSingleton();
        }
        
        //调用实例
        public static InnerSingleton getSingleton(){
            return InnerInSingleton.singleton;
        }
    }

    再运行用例1 的代码,则会报错:

    Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at com.qrTest.singleton.Test.main(Test.java:14)
    Caused by: java.lang.IllegalArgumentException: 不能创造InnerSingleton两次!
        at com.qrTest.singleton.InnerSingleton.<init>(InnerSingleton.java:10)
        ... 5 more

    解决了多次实例化的问题。

    当单例模式需要序列化的时候,新的问题又来了;
    上面的几种单例模式都是可以序列化的,实现Serializable就可以实现序列化,为了保证序列化的时候实例还是Singleton,必须声明所有的实例域都是transient的,并且提供 readResolve方法,否则,每次反序列化都会生成新的实例。
    例3:以内部静态类单例模式为例

    public class InnerSingleton implements Serializable{
        //私有构造方法
        private InnerSingleton(){
            
        }
        /**
         * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
         * 而且只有被调用时才会被装载,从而实现了延迟加载
         */
        private static class InnerInSingleton{
            //静态的初始化器,由jvm来保证线程安全
            private static InnerSingleton singleton = new InnerSingleton();
        }
        
        //调用实例
        public static InnerSingleton getSingleton(){
            return InnerInSingleton.singleton;
        }
        
        //测试
        public static void main(String[] args) throws Exception, IOException {
            InnerSingleton singleton = InnerSingleton.getSingleton();
            
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
            oos.writeObject(singleton);
            oos.close();
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
            InnerSingleton singleton2 = (InnerSingleton) ois.readObject();
            ois.close();
            
            System.out.println(singleton == singleton2);
        }
    }

    输出:

    false

    加入 readResolve 方法后:

    public class InnerSingleton implements Serializable{
        //私有构造方法
        private InnerSingleton(){
            
        }
        /**
         * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
         * 而且只有被调用时才会被装载,从而实现了延迟加载
         */
        private static class InnerInSingleton{
            //静态的初始化器,由jvm来保证线程安全
            private static InnerSingleton singleton = new InnerSingleton();
        }
        
        //调用实例
        public static InnerSingleton getSingleton(){
            return InnerInSingleton.singleton;
        }
        
        //加入readResolve方法
        public Object readResolve() {
            return InnerSingleton.getSingleton();
        }
        
        //测试
        public static void main(String[] args) throws Exception, IOException {
            InnerSingleton singleton = InnerSingleton.getSingleton();
            
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
            oos.writeObject(singleton);
            oos.close();
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
            InnerSingleton singleton2 = (InnerSingleton) ois.readObject();
            ois.close();
            
            System.out.println(singleton == singleton2);
        }
    }

    输出:

    true

    通过此方法保证了反序列化单例的唯一。

    单例类和枚举
    单元素的枚举类型已经成为Singleton的最佳方法。

    public enum Singleton{
        //定义一个枚举元素,它就代表了Singleton的一个实例
        Instance;
        //单例也可以有自己的操作
        public void singletonOpt(){
            //操作
        }
    }

    使用枚举来实现单实例的控制会更加的简洁,而且无偿的提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化;

    枚举类型也可以防止反射攻击,当你试图通过反射去实例化一个枚举类型的时候抛出IllegalArgumentException:Cannot reflectively create enum objects 异常,是更加简洁、高效、安全的实现单例的方式。

    此篇文章参考:https://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html,LZ个人觉得这个博主的java设计模式图文并茂讲解的非常好;欢迎大家去阅读。

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    IDEA解决Cannot download sources的问题
    Swagger在Springboot项目中的使用
    ElasticSearch(10)—SpringBoot集成ES
    ElasticSearch(9)---Rest风格
    ElasticSearch(8)---IK分词器
    js显示原型和隐示原型
    通俗易懂讲解为什么设计稿都是750px
    关于rem和px全局设置问题
    PHP RSA密文过长加密解密 越过1024的解决代码
    使用https,$_SERVER['HTTPS']却不等于on?
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9635169.html
Copyright © 2020-2023  润新知