设计模式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

  

 

posted @ 2020-04-29 20:26  我爱钻研  阅读(239)  评论(0)    收藏  举报