zoukankan      html  css  js  c++  java
  • 设计模式03-单例模式

    1.3.1.设计模式03-单例模式【上】

    时长:1h33min

    课程目标

    》单例模式的应用场景

    》IDEA下的多线程调试方式

    》保证线程安全的单例模式策略

    》掌握反射暴力攻击单例解决方案及原理分析

    序列化破坏单例的原理及解决方案

    》掌握常见的单例模式写法

    原型模式的应用场景及常用写法

    学习目的

      》深入掌握单例模式

      》解决面试题

    1.单例模式的定义

      单例模式,Singleton Pattern.

    定义:

      是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

      隐藏所有的构造方法【私有】

      创建创建型模式。

    1.1.适用场景

      确保在任何情况下都只有一个实例。

    生活中单例体现:

      国家主席,公司ceo,部门经理。

    代码中单例类:

      ServletContext,ServletConfig,ApplicationContext,DBPool

    为什么它是单例的呢?

      如:配置文件,只能有一个,否则该使用谁呢。【唯一性主权】

    2.单例模式常见用法

    1.饿汉式单例

    2.懒汉式单例

    3.注册式单例

    4.ThreadLocal单例

    2.1.饿汉式单例

      人很饿------》着急吃饭---》在类首次加载时创建实例【用不用,先吃饱再说】

    2.1.1.代码示例
    package com.wf.singleton;
    
    /**
     * @ClassName HungrySingleton
     * @Description 饿汉式单例
     * @Author wf
     * @Date 2020/4/29 17:28
     * @Version 1.0
     */
    public class HungrySingleton {
        //首次加载静态成员
        public static final  HungrySingleton singleton = new HungrySingleton();
        //构造私有
        private HungrySingleton(){}
        //全局访问点
        public static HungrySingleton getInstance(){
            return singleton;
        }
    }

    第二种写法:

    package com.wf.singleton;
    
    /**
     * @ClassName HungryStaticSingleton
     * @Description 饿汉式单例---静态块初始化
     * @Author wf
     * @Date 2020/4/29 17:31
     * @Version 1.0
     */
    public class HungryStaticSingleton {
        //final保证反射不会改变实例
        private static final HungryStaticSingleton instance;
        private HungryStaticSingleton(){}
        static{
            instance = new HungryStaticSingleton();
        }
        public static HungryStaticSingleton getInstance(){
            return instance;
        }
    }
    2.1.2.优缺点总结

    优点:

      执行效率高,性能高【无锁】

    缺点

      可能生成内存浪费,因为实例不被使用,也进行new .

    说明:

      这种写法,只能在小范围内使用。

    2.2.懒汉式单例

      人很懒------不着急-----等到使用时再创建

    2.2.1.示例代码
    package com.wf.singleton;
    
    /**
     * @ClassName LazySimpleSingleton
     * @Description 懒汉式单例
     * @Author wf
     * @Date 2020/4/29 17:43
     * @Version 1.0
     */
    public class LazySimpleSingleton {
        private static LazySimpleSingleton instance;
        private LazySimpleSingleton(){}
    
        public static LazySimpleSingleton getInstance(){
            if(null == instance) {
                instance = new LazySimpleSingleton();
            }
            return instance;
        }
    }
    2.2.2.总结

    优点:

      节省内存空间,使用时再创建实例。

    缺点:

      线程不安全。性能差。

      为什么会线程不安全呢?

      线程不安全来自于getInstance方法,方法的调用者最终归结到线程。

    当出现多个线程,同时调用getInstance方法时,线程间会抢占资源。如下所示:

     这是理论上的分析。下面在idea中进行代码调试,说明这种线程不安全性。

    A.多线程调试线程安全性

    【1】创建测试类

    package com.wf.singleton;
    
    import org.junit.Test;
    
    public class LazySimpleSingletonTest {
    
        @Test
        public void getInstance() {
            Thread t1 = new Thread(new ExecutorThread());
            Thread t2 = new Thread(new ExecutorThread());
            t1.start();
            t2.start();
            System.out.println("end");
        }
    }

    【2】测试任务类

    package com.wf.singleton;
    
    /**
     * @ClassName ExecutorThread
     * @Description 创建一个测试task
     * @Author wf
     * @Date 2020/4/29 18:53
     * @Version 1.0
     */
    public class ExecutorThread implements Runnable {
        @Override
        public void run() {
            LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() +":"+instance);
        }
    }

    在任务类中,run方法,访问单例类,获取实例。并打印实例hash地址。当两个线程,打印hash地址相同时,

    说明只有一个实例,是单例的。

    不存在线程安全问题,否则,是线程非安全的。

    B.调试过程

    【1】直接运行测试类,查看结果:

    明显,可以看到,hash地址不同,创建现两个实例。

     

    多次运行,可以发现,有时只会创建一个实例。这时,线程执行的随机性决定的。   

    我们可以得出结论,getInstance方法是线程非安全的。

    【2】断点调试之前,理论上结果分析

    从上面的运行,有两种结果:

    》同一实例

      a,两个线程顺序执行

      b.两个线程都进入getInstance方法,if分支【后者覆盖前者

        第一个线程,创建出一个实例instance1

        第二个线程,稍后第二个线程,也进入if,并执行new实例instance2,instance2就把instance1给覆盖掉了

        最后,得到的实例都是instance2

    》不同实例

      同时进入if条件,按顺序返回

      第一个线程new后,快速执行结束方法,打印实例1

      第二个线程,再次new,打印实例2

    【3】断点调试

    断点设置如下:

     

    注意:

      3个断点,都设置成Thread多线程运行模式。设置方法:右击断点,弹出设置框,切换到Thread模式,然后Done.

    然后,debug运行测试类:

    在Frames窗口下,可以查看到所有的线程。现在,几个线程都是running状态,表示线程都抢到cpu资源,但是什么时候,

    执行任务,由cpu来决定。

    切换到Thread-0,然后执行下一步,先让它按正常状态执行。

    Thread-0线程,先顺序执行完成,并打印.

      然后,切换到第二个线程Thread-1,让他进入getInstance方法,经过if判断,由于instance已经创建,非null,条件不成立。

    所以,两个线程创建一个实例。如下所示:

    可以看到,最后打印相同的结果。

    第二种情况 :调试后者覆盖前者

    先让两个线程都进入if分支,如下所示:

    Thread-0线程进入:

     Thread-1线程进入:

    然后,再切换到Thread-0,先执行new语句,如下所示:

    再切换到,线程1,执行new操作,如下所示:

    再切换到,Thread-0执行完成,打印结果。

     再让Thread-1执行完成,打印结果:

     

     第三种情况:不同实例

    首先,让两个线程都进入getInstance方法的if分支。

    然后,先让Thread-0线程执行完,并打印。

    最后,Thread-1线程,再执行完成,并打印,如下所示:

    出现不同的实例。

    2.2.3.解决线程不安全

    解决方案:

    使用sycronized关键字,加同步锁,如下所示:

    package com.wf.singleton;
    
    /**
     * @ClassName LazySimpleSingleton
     * @Description 懒汉式单例
     * @Author wf
     * @Date 2020/4/29 17:43
     * @Version 1.0
     */
    public class LazySimpleSingleton {
        private static LazySimpleSingleton instance;
        private LazySimpleSingleton(){}
    
        public synchronized static LazySimpleSingleton getInstance(){//使用synchronized解决线程安全性问题
            if(null == instance) {
                instance = new LazySimpleSingleton();
            }
            return instance;
        }
    }

    问题解决了。

     2.2.3.1.debug分析synchronized关键字作用

    先让Thread-0线程进入if分支,如下所示:

    可以观察到,两个线程的状态均为Running.

    再切换到Thread-1线程,也试图让它进入getInstance方法:

    可以发现,Thread-1线程进不去getInstance方法了,并且线程状态变为Monitor.

    也就是说Thread-1线程阻塞,开始等待,直到Thread-0线程执行完getInstance方法。

    然后,让Thread-0线程执行完,并打印,如下所示:

    然后,切换到Thread-1线程,如下所示:

    可以发现,当Thread-0线程执行后,切换到Thread-1线程,线程状态又恢复到Running状态,并进入getInstance方法。

    所以,我们得出结论:

      synchronized关键字,是用来控制资源只能被一个线程占用,实现互斥特性

      虽然,解决了线程安全性问题,但是代码性能受到限制。性能差

      为了解决性能问题,提出“双重检查”单例的解决方案。

     2.2.4.双重检查的单例模式
    2.2.4.1.代码示例如下:
    package com.wf.singleton;
    
    /**
     * @ClassName LazyDoubleCheckSingleton
     * @Description  双重检查--懒汉式单例
     * @Author wf
     * @Date 2020/4/30 12:41
     * @Version 1.0
     */
    public class LazyDoubleCheckSingleton {
        //volatile关键字解决指令重排序问题
        private volatile static LazyDoubleCheckSingleton instance;
        private LazyDoubleCheckSingleton(){}
        
        public  static LazyDoubleCheckSingleton getInstance(){
            if(null == instance){//第一次检查,是否阻塞,只阻塞第一次new
                synchronized (LazyDoubleCheckSingleton.class) {
                    //第二次检查,检查是否重复创建实例
                    if (null == instance) {
                        instance = new LazyDoubleCheckSingleton();
                        //会有指令重排序问题
                    }
                }
            }
            return instance;
        }
    }

    当在getInstance方法内部使用双重检查机制,解决了懒汉式单例的性能问题。

    但它存在指令重排序问题。

    创建一个对象包含下面两个过程:

    1、类构造器完成类初始化(分配内存、赋予默认值)

    2、类实例化(赋予给定值)

    在java中,当new一个实例时,会存在两块内存地址的处理:

      》创建后的实例会指向堆内存

      》成员赋值会指向栈内存

      两块内存的操作,先后顺序具有随机性,因此,可能会产生指令重排序问题。

       不过,没关系,java提供了volatile关键字,解决指令重排序问题。

     2.2.4.2.总结

      双重检查机制,解决了线程安全性问题,也提升程序性能

      但是,两个if判断,很容易让人迷惑可读性差,代码不够优雅

    1.3.2.设计模式03-单例模式【下】

     时长:1h6min

    2.2.5.懒汉式-静态内部类实现单例

    代码如下:

    package com.wf.singleton;
    
    /**
     * @ClassName LazyStaticInnerClassSingleton
     * @Description 懒汉式-静态内部类实现单例
     * @Author wf
     * @Date 2020/4/30 13:11
     * @Version 1.0
     */
    public class LazyStaticInnerClassSingleton {
        private LazyStaticInnerClassSingleton(){}
    
        private static LazyStaticInnerClassSingleton getInstance(){
            return LazyHolder.instance;
        }
        //静态内部类
        private static class LazyHolder{
            private static LazyStaticInnerClassSingleton instance =
                    new LazyStaticInnerClassSingleton();
    
        }
    }

    为什么这种方式是懒汉式呢,感觉很类似饿汉式单例?

      类加载机制,默认加载classpath下的*.class文件,而内部类在classpath下编译为XXX$LazyHolder.class【主类$内部类.class】

      所以,首次加载类时,不会加载内部类。只有当程序中使用到内部类时,才会加载内部类。

    总结:

     优点:写法优雅,很好利用java本身的语法特点,性能高,避免内存浪费。

     缺点:无法避免反射破坏

    实际上,前面的几种单例写法,都无法避免反射破坏。

    2.2.6.反射破坏单例测试

      为什么反射会破坏单例呢?

      因为代码中单例的实现时,基于构造器私有。如果通过反射能拿到类的实例,不就被破坏了吗。

    2.2.6.1.测试代码
    package com.wf.singleton;
    
    import java.lang.reflect.Constructor;
    
    /**
     * @ClassName ReflectTest
     * @Description 反射破坏单例
     * @Author wf
     * @Date 2020/4/30 13:52
     * @Version 1.0
     */
    public class ReflectTest {
        public static void main(String[] args) {
            Class<?> clazz = LazyStaticInnerClassSingleton.class;
            try {
                Constructor<?> c = clazz.getDeclaredConstructor(null);
                System.out.println(c);
                c.setAccessible(true);//强制
                Object obj = c.newInstance();//通过反射,成功绕过单例类的全局访问点,创建实例
                Object obj1 = c.newInstance();
                System.out.println(obj);
                System.out.println(obj1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    测试结果如下:

    很明显,单例特性已经被破坏。

    2.2.6.2.如何防止反射破坏单例

      解决方法,是在构造器中做非空判断,如果非空,抛异常,提示不允许非法访问。

    代码如下:

    package com.wf.singleton;
    
    /**
     * @ClassName LazyStaticInnerClassSingleton
     * @Description 懒汉式-静态内部类实现单例
     * @Author wf
     * @Date 2020/4/30 13:11
     * @Version 1.0
     */
    public class LazyStaticInnerClassSingleton {
        private LazyStaticInnerClassSingleton(){
            //防止反射破坏单例
            if(null != LazyHolder.instance){
                throw new RuntimeException("不允许非法访问");
            }
        }
    
        private static LazyStaticInnerClassSingleton getInstance(){
            return LazyHolder.instance;
        }
        //静态内部类
        private static class LazyHolder{
            private static LazyStaticInnerClassSingleton instance =
                    new LazyStaticInnerClassSingleton();
    
        }
    }

    然后,执行测试:

    反射破坏单例,成功解决。

    总结:

      这种在构造器上做判断,抛异常的方式。使代码看起来很奇怪,代码又不够优雅了。程序可读性变差。

      那么,什么才是优雅的单例写法呢?

      答案是:注册式单例,它是《java Effective》一书中推荐的,最为优雅的单例写法。

    3.注册式单例

    定义:

      将每一个实例都缓存到统一的容器中,使用唯一标识获取实例。

    3.1.枚举式单例

      枚举式单例,属于一种特殊的注册式单例。

      java中提供enum关键字,标识一个类为枚举类。底层它是继承Eunm抽象类。

    3.1.1.示例代码如下
    package com.wf.singleton.register;
    
    /**
     * @ClassName EnumSingleton
     * @Description 枚举式单例
     * @Author wf
     * @Date 2020/4/30 14:16
     * @Version 1.0
     */
    public enum EnumSingleton {
        INSTANCE;
        //枚举中可以自定义属性
        private Object data;
        
        public static EnumSingleton getInstance(){
            return INSTANCE;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    }

    枚举式单例应该怎么使用呢?

    创建一个测试类,模拟客户端调用

    package com.wf.singleton;
    
    import com.wf.singleton.register.EnumSingleton;
    
    import java.lang.reflect.Constructor;
    
    /**
     * @ClassName EnumSingletonTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/30 14:21
     * @Version 1.0
     */
    public class EnumSingletonTest {
        public static void main(String[] args) {
            EnumSingleton instance = EnumSingleton.getInstance();
            instance.setData("设置值");
    
            //测试反射破坏
            Class<?> clazz = EnumSingleton.class;
            try {
                Constructor<?> c = clazz.getDeclaredConstructor(null);
                Object obj1 = c.newInstance();
                Object obj2 = c.newInstance();
                System.out.println("obj1:"+obj1);
                System.out.println("obj2:"+obj2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    测试结果如下:

    可以发现,进行反射破坏时,代码抛异常了。异常信息如下:

    java.lang.NoSuchMethodException: com.wf.singleton.register.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at com.wf.singleton.EnumSingletonTest.main(EnumSingletonTest.java:22)

    通过查看源码,Enum类只有一个带2个参数的构造器,那么反射破坏是不是可以基于此创建实例呢,修改代码如下:

    package com.wf.singleton;
    
    import com.wf.singleton.register.EnumSingleton;
    
    import java.lang.reflect.Constructor;
    
    /**
     * @ClassName EnumSingletonTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/30 14:21
     * @Version 1.0
     */
    public class EnumSingletonTest {
        public static void main(String[] args) {
            EnumSingleton instance = EnumSingleton.getInstance();
            instance.setData("设置值");
    
            //测试反射破坏
            Class<?> clazz = EnumSingleton.class;
            try {
                Constructor<?> c = clazz.getDeclaredConstructor(String.class,int.class);
                System.out.println(c);//可以得到
                c.setAccessible(true);
                Object obj = c.newInstance();
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    测试结果,报错。提示 不允许反射破坏。

    为什么这样呢?意味着枚举类,没有getDeclaredConstructor方法。

    3.1.2.查看枚举类的源码Enum 
    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable {
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    可以发现,Enum类中并不存在无参构造,只有一个带2个参数的构造器。

    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);
            }
        }    //修饰符如果是Enum枚举,不允许反射调用
        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;
    }
    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }
     
    
    Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }
    //定义map容器
    private volatile transient Map<String, T> enumConstantDirectory = null;
    3.1.3.总结

    对于枚举来说,只要实例创建完成,就会被当成常量,存入map容器中。

    不管用不用,都会存在。

    所以,它是一种饿汉式单例。可能造成内存浪费。不能大批量创建对象

    优点:

      代码优雅,底层防止反射破坏。线程安全。

     因为它不能大量创建对象,所以,在spring中并没有使用枚举来实现单例。

     而是基于枚举的设计思想,spring创建ioc容器来实现单例。

    3.2.容器式单例

    3.2.1.示例代码
    package com.wf.singleton;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @ClassName ContainerSingleton
     * @Description 容器式单例
     * @Author wf
     * @Date 2020/4/30 19:37
     * @Version 1.0
     */
    public class ContainerSingleton {
        private ContainerSingleton(){}
    
        private static Map<String,Object> iocMap = new ConcurrentHashMap<>();
    
        public static Object getInstance(String className){
            if(!iocMap.containsKey(className)){
                try {
                    Object instance = Class.forName(className).newInstance();
                    iocMap.put(className,instance);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            return iocMap.get(className);
        }
    }
    3.2.2.测试代码
    package com.wf.singleton;
    
    /**
     * @ClassName ContainerSingletonTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/30 19:42
     * @Version 1.0
     */
    public class ContainerSingletonTest {
        public static void main(String[] args) {
            Object inst1 = ContainerSingleton.getInstance("com.wf.singleton.User");
            Object inst2 = ContainerSingleton.getInstance("com.wf.singleton.User");
    
            System.out.println(inst1);
            System.out.println(inst2);
        }
    }
    package com.wf.singleton;
    
    /**
     * @ClassName User
     * @Description TODO
     * @Author wf
     * @Date 2020/4/30 19:43
     * @Version 1.0
     */
    public class User {
    
    }

    测试结果,如下:

    3.2.3.总结

      容器式单例,可以解决大量创建单例对象的问题。但它存在线程安全性问题。

      那么,如何解决线程安全性问题呢?【可以从spring中寻找解决方案】,使用synchnonized双重检查锁来解决。

    3.3.序列化破坏单例

    3.3.1.什么是序列化和反序列化?

      把内存中的对象状态,转换为字节码形式,然后再把字节码通过IO流输出,写到磁盘文件,保存到一个文件中。

    实现永久化保存的这个过程,就是 序列化

      反之,再次使用时,从磁盘文件,读取内容到内存中,称为反序列化

    3.3.2.序列化破坏单例的代码示例

      java中实现序列化,只需要实现Serialzable接口。

    package com.wf.singleton;
    
    import java.io.Serializable;
    
    /**
     * @ClassName SerializableSingleton
     * @Description 序列化破坏单例示例
     * @Author wf
     * @Date 2020/4/30 20:03
     * @Version 1.0
     */
    public class SerializableSingleton implements Serializable {
        private static final SerializableSingleton instance = new SerializableSingleton();
        private SerializableSingleton(){}
        
        public static SerializableSingleton getInstance(){
            return instance;
        }
        
       /* private Object readResolve(){
            return instance;
        }*/
    }

    创建测试类:

    package com.wf.singleton;
    
    import java.io.*;
    
    /**
     * @ClassName SerializableSingletonTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/30 20:08
     * @Version 1.0
     */
    public class SerializableSingletonTest {
        public static void main(String[] args) {
            SerializableSingleton s1 = null;
            SerializableSingleton s2 = SerializableSingleton.getInstance();
            FileOutputStream fos = null;
            try{
                fos = new FileOutputStream("serializableSingleton.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(s2);
    
                oos.flush();
                oos.close();
    
                FileInputStream fis = new FileInputStream("serializableSingleton.obj");
                ObjectInputStream ois = new ObjectInputStream(fis);
                s1 = (SerializableSingleton) ois.readObject();
                ois.close();
    
                System.out.println(s1);
                System.out.println(s2);
                System.out.println(s1==s2);
            }catch (Exception e){
    
            }
        }
    }

    测试结果如下:

    显然,单例被破坏了。

    如何解决序列化破坏单例的问题呢,在单例类中增加readResolve方法如下:

    package com.wf.singleton;
    
    import java.io.Serializable;
    
    /**
     * @ClassName SerializableSingleton
     * @Description 序列化破坏单例示例
     * @Author wf
     * @Date 2020/4/30 20:03
     * @Version 1.0
     */
    public class SerializableSingleton implements Serializable {
        private static final SerializableSingleton instance = new SerializableSingleton();
        private SerializableSingleton(){}
    
        public static SerializableSingleton getInstance(){
            return instance;
        }
        //防止序列化破坏单例
        private Object readResolve(){
            return instance;
        }
    }

    再进行测试,结果如下:

    为什么,增加readResolve方法,就解决这个问题了呢?

    创建对象,是由java.io.ObjectInputStream#readObject方法来完成的。下面查看它的源码。

    3.3.2.readObject源码分析
    public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {//允许重写
            return readObjectOverride();
        }
    
        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);//核心逻辑
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }
    
    private Object readObject0(boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }
    
        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }
    
        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();
    
                case TC_REFERENCE:
                    return readHandle(unshared);
    
                case TC_CLASS:
                    return readClass(unshared);
    
                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);
    
                case TC_STRING:
                case TC_LONGSTRING://checkResovle方法
                    return checkResolve(readString(unshared));
    
                case TC_ARRAY:
                    return checkResolve(readArray(unshared));
    
                case TC_ENUM:
                    return checkResolve(readEnum(unshared));
    
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));
    
                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);
    
                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }
    
                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }
    
                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }
    
    private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }
    
        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();
    
        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }
    
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
    
        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }
    
        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }
    
        handles.finish(passHandle);
    
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);//核心逻辑
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }
    
        return obj;
    }
    
    Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {//如果有readResolve方法,就会调用readResolve的结果
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

    当不存在readResolve方法时,就会执行newInstance方法,创建实例,导致单例被坏

    4.ThreadLocal单例

      ThreadLocal线程局部,创建一个与线程绑定的变量,所以,它能隔离线程。

    它天生能保证线程安全。

     4.1.示例代码
    package com.wf.singleton.threadlocal;
    
    /**
     * @ClassName ThreadLocalSingleton
     * @Description TODO
     * @Author wf
     * @Date 2020/5/6 12:18
     * @Version 1.0
     */
    public class ThreadLocalSingleton {
        private static final ThreadLocal<ThreadLocalSingleton> theadLocalInstance = 
                new ThreadLocal<ThreadLocalSingleton>(){
                    @Override
                    protected ThreadLocalSingleton initialValue() {
                        return new ThreadLocalSingleton();
                    }
                };
        
        private ThreadLocalSingleton(){}
        
        public static ThreadLocalSingleton getInstance(){
            return theadLocalInstance.get(); 
        }
        
    }

    测试代码:

    package com.wf.singleton;
    
    import com.wf.singleton.threadlocal.ThreadLocalSingleton;
    
    /**
     * @ClassName ThreadLocalSingletonTest
     * @Description TODO
     * @Author wf
     * @Date 2020/5/6 12:24
     * @Version 1.0
     */
    public class ThreadLocalSingletonTest {
        public static void main(String[] args) {
            System.out.println(ThreadLocalSingleton.getInstance());
            System.out.println(ThreadLocalSingleton.getInstance());
            System.out.println(ThreadLocalSingleton.getInstance());
        }
    }

    测试结果如下:

    多次获取实例,得到同一实例。显然是满足单例的。

    但是,ThreadLocal有其特殊性,它是线程绑定的。即不同线程获得实例,可能是不同的。

    4.1.1.ThreadLocal单例特殊性测试
    package com.wf.singleton;
    
    import com.wf.singleton.threadlocal.ThreadLocalSingleton;
    
    /**
     * @ClassName ThreadLocalSingletonTest
     * @Description TODO
     * @Author wf
     * @Date 2020/5/6 12:24
     * @Version 1.0
     */
    public class ThreadLocalSingletonTest {
        public static void main(String[] args) {
            System.out.println(ThreadLocalSingleton.getInstance());
            System.out.println(ThreadLocalSingleton.getInstance());
            System.out.println(ThreadLocalSingleton.getInstance());
    
            Thread t1 = new Thread(()->{
                System.out.println(Thread.currentThread().getName()+":"+ThreadLocalSingleton.getInstance());
    
            });
            Thread t2 = new Thread(() ->{
                System.out.println(Thread.currentThread().getName()+":"+ThreadLocalSingleton.getInstance());
            });
            t1.start();
            t2.start();
            System.out.println("end");
        }
    }

    测试结果如下:

    说明:

      似乎是产生了三个不同的实例。似乎是破坏了单例。但这是正常的。

      ThreadLocal只能保证同一线程下,只有一个实例。

      所以,这种单例是一种局限性单例。有其特殊的应用场景。

    4.2.ThreadLocal的原理探究

    核心方法:get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取map 
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//key是this,表示当前对象所在线程
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    线程的原因,是内部存在一个map维护数据,key是当前对象的所在的线程。

    5.单例在源码中应用

    spring中AbstractFactoryBean#getObject

    Mybatis中ErrorContext[ThreadLocal的应用]

    6.单例模式的总结

    6.1.优点

      在内存中只有一个实例,减少了内存开销

      可以避免对资源的多重占用。

      设置全局访问点,严格控制访问

     6.2.缺点

      不能面向接口编程。扩展困难

      如果要扩展单例对象,只能修改代码。【某种意义上说,违背开闭原则】

    6.3.相关知识点总结

    1.构造器私有

    2.保证线程安全性

    3.延迟加载【懒汉式--使用才创建】

    4.防止序列化和反序列化破坏单例

    5.防御反射破坏单例

    6.4.补充知识---容器式单例的线程安全性解决

    AbstractBeanFactory----getBean--->doGetBean--->getObjectForBeanInstance--->getObjectFromFactoryBean

    可以知道,使用synchronized同步锁

    附录:

    idea自动生成junit测试类:

    两种快捷键:

    1.在要生成测试类的类里面,按ctrl+shift+t –> create new test

      

  • 相关阅读:
    集合(5)—Map之HashMap()
    《转》python 10 集合
    《转》python8元组
    《转》python(7)列表
    《转》python数据类型
    《转》python对象
    《转》python
    《转》python基础下
    《转》python学习基础
    《转》python 9 字典,numpy
  • 原文地址:https://www.cnblogs.com/wfdespace/p/12804435.html
Copyright © 2011-2022 走看看