设计模式:深入研究单例设计模式(Java版)

简介: 创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。单例(Singleton)模式:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。单例是一种常用的设计模式,属于创建型设计模式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确

四、单例设计模式

4.1 创建型设计模式简介

创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。

创建型模式分为以下5种。

  • 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
  • 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
  • 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
  • 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
  • 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

以上 5 种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式,我们将在之后的教程中详细地介绍它们的特点、结构与应用。

4.2 单例设计模式简介

  • 单例(Singleton)模式:指一个类只有一个实例,且该类能自行创建这个实例的一种模式

例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例是一种常用的设计模式,属于创建型设计模式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

  • 1)某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。单例能够降低系统内存的使用频率,减轻GC压力。
  • 2)有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

4.3 单例设计模式的实现

Singleton 模式通常有饿汉式和懒汉式两种实现形式;

4.3.1 饿汉式

  • 单例类:
package com.pattern.demo01_饿汉式;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton {
    // 私有化成员变量(在类加载的时候就已经创建了)
    private static Singleton singleton=new Singleton();
    // 私有化构造(不允许new)
    private Singleton(){}
    // 对外提供公共的获取方法
    public static Singleton getSingleton(){
        return singleton;
    }
}
  • 测试类:
package com.pattern.demo01_饿汉式;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) throws Exception {
        Singleton s1 = Singleton.getSingleton();
        Singleton s2 = Singleton.getSingleton();
        System.out.println(s1 == s2);
    }
}

饿汉式单例写法在类加载的时候就已经创建好了该类的单例对象;一般用于单例对象较少的情况下,饿汉式有一个明显的缺点,就是所有对象在类加载的时候就实例化,而不是等到使用的时候再实例化,这样一来如果系统中有大量的单例对象存在,则在系统初始化时会造成大量的内存浪费;不管对象是否使用,都占着内存空间;

4.3.2 懒汉式

懒汉式也是实现单例设计模式中的一种方式;

  • 单例类:
package com.pattern.demo01_单例设计模式的实现;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton {
    // 定义成员变量
    private static Singleton singleton = null;
    // 私有化构造方法
    private Singleton() {
    }
    // 提供方法供外界访问
    public static Singleton getSingleton() {
        if (singleton != null) {
            return new Singleton();
        }
        return null;
    }
}

Tips:需要用到的时候(调用方法的时候)再创建

  • 测试类:
package com.pattern.demo01_单例设计模式的实现;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getSingleton();
        Singleton s2 = Singleton.getSingleton();
        System.out.println(s1 == s2);     // true
    }
}

相对比与饿汉式,懒汉式在使用到单例对象时才创建对象,相比于饿汉式节约了内存;

4.4 线程安全与单例设计模式

4.4.1 线程安全问题

为了解决饿汉式带来的内存浪费问题,我们使用懒汉式的单例设计模式写法,其特点是单例对象在使用时才会被实例化,但是这会引来一个新的问题——线程安全问题;如果在多线程环境下,那么可能会导致类被实例化多次;

  • 修改单例类:
package com.pattern.demo03_懒汉式_线程安全问题;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton {
    private static Singleton singleton = null;
    public static Singleton getSingleton() {
        if (singleton == null) {
            try {
                Thread.sleep(10);     //  为了更加的方便看出效果,这里睡眠10ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new Singleton();
        }
        return singleton;
    }
}
  • 测试类:
package com.pattern.demo03_懒汉式_线程安全问题;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
    }
}

执行结果:

4.4.2 解决线程安全问题

为了解决线程安全问题,我们可以使用synchronized关键字来实现线程同步;

package com.pattern.demo04_懒汉式_synchronized;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton {
    private static Singleton singleton = null;
    public static Singleton getSingleton() {
        synchronized (Singleton.class){         // 关键的代码锁住(保证同一时间只能有一个线程进来判断)
            if (singleton == null) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleton = new Singleton();
            }
            return singleton;
        }
    }
}
  • 测试类(代码不变):
package com.pattern.demo04_懒汉式_synchronized;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
    }
}

再次运行测试类:发现多条线程创建的对象仍是同一个

4.4.3 双重校验锁方案

我们前面通过synchronized同步代码块可以实现多线程下同步问题;但是如果在线程数量非常大的情况下,使用synchronized加锁,则会导致大量的线程阻塞,程序性能会大幅下降;想要保证线程安全问题,有想要性能上的提升,我们可以使用双重校验锁

  • synchronized方案:

  • 未进行双重校验:

  • 双重校验:

  • 双重校验锁判断:
package com.pattern.demo05_双重校验锁;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton {
    private static Singleton singleton = null;
    public static Singleton getSingleton() {
        // 检查是否需要阻塞(并不是所有的线程上来就阻塞)
        if (singleton == null) {
            // 获取到锁的线程才进去
            synchronized (Singleton.class){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 重新判断,因为前面有可能多条线程都通过了if判断
                if(singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • 测试类(代码不变):
package com.pattern.demo05_双重校验锁;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton = Singleton.getSingleton();
                System.out.println(Thread.currentThread().getName() + "【" + singleton + "】");
            }
        }.start();
    }
}

再次运行测试类:发现多条线程创建的对象仍是同一个

4.5 静态内部类与单例设计模式

4.5.1 静态内部类方案

双重校验锁单例写法虽然解决了线程安全问题和性能问题,但是只要用到synchronized关键字总是要加锁,对性能还是存在一定的影响;我们可以从类加载的角度初始化,采用静态内部类来实现单例设计模式;

  • 小案例:
package com.pattern.demo06_静态内部类;
/**
 * @author lscl
 * @version 1.0
 * @intro: 测试静态内部类加载顺序
 */
public class TestInnerClass {
    public static class StaticInnerTest {
        static {
            System.out.println("静态内部类加载了....");
        }
        static void staticInnerMethod() {
            System.out.println("静态内部类方法调用了....");
        }
    }
}
  • 测试类:
package com.pattern.demo06_静态内部类;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_静态内部类加载顺序 {
    public static void main(String[] args) {
        // 创建外部类时,静态内部类不会随着外部类的加载而加载
        TestInnerClass testInnerClass = new TestInnerClass();
        System.out.println("=======================");
        TestInnerClass.StaticInnerTest.staticInnerMethod();
    }
}

输出结果:

=======================
静态内部类加载了....
静态内部类方法调用了....

从输出结果可以得出结论:加载一个类时,其内部类不会被同时加载。

Tips:类何时被加载?


  • 1)实例化对象
  • 2)创建了子类的实例
  • 3)访问静态方法、静态变量等
  • 4)获取字节码对象的时候
  • 1)对象.getClass()
  • 2)调用类的class属性
  • 3)class.forName()


  • 5)加载了子类(子类调用静态成员、new对象等情况)

知道了静态内部类的加载顺序后,我们可以利用这一特点来实现懒汉式单例设计模式

  • 单例类:
package com.pattern.demo06_静态内部类;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton {
    // 私有化成员
    private static Singleton singleton = null;
    // 私有构造方法
    private Singleton() {}
    // 提供公共的访问方法
    public static Singleton getSingleton(){
        return Holder.INSTANCE;
    }
    // 使用静态内部类来初始化
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
  • 测试:
package com.pattern.demo06_静态内部类;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_静态内部类实现单例设计模式 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getSingleton();    // 在这一步实例化了单例类  
        Singleton s2 = Singleton.getSingleton();
        System.out.println(s1 == s2);           // true
    }
}

Tips:静态内部类方案不会存在线程安全问题;静态内部类单例模式是一种优秀的单例模式,是项目中比较常用的一种单例模式(即使存在后续的问题)。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

4.5.2 反射破坏单例设计模式

我们之前的设计方案不管是饿汉式还是懒汉式都是将单例类的构造方法私有,让外部无法直接通过new关键字来实例化对象;但是我们之前学习过反射,通过反射我们可以在外部访问任意修饰符修饰的任意方法;也就是说,我们之前设计的单例方案不管哪一种都可以使用反射来破解;

  • 反射破解静态内部类方案:
package com.pattern.demo06_静态内部类;
import java.lang.reflect.Constructor;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_反射破坏静态内部类单例设计模式 {
    public static void main(String[] args) throws Exception {
        // 获取任意修饰符修饰的构造方法
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        // 设置强制访问
        constructor.setAccessible(true);
        Singleton s1 = constructor.newInstance();           // 相当于new
        Singleton s2 = constructor.newInstance();
        System.out.println(s1 == s2);           // false
    }
}

Tips:这里只演示了破坏静态内部类方式的单例设计模式,反射可以破坏我们前面学习过的任意单例设计模式;

4.5.3 防止反射破坏单例

  • 重新改进单例类:
package com.pattern.demo07_静态内部类防止反射破坏单例设计模式;
import java.io.Serializable;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton implements Serializable {
    // 私有化成员
    private static Singleton singleton = null;
    // 私有化构造方法
    private Singleton() {
        if (Holder.INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");            // 如果通过构造方法来创建对象则抛出异常
        }
    }
    // 共有访问方法
    public static Singleton getSingleton() {
        return Holder.INSTANCE;
    }
    // 静态内部类
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
  • 测试类1:
package com.pattern.demo07_静态内部类防止反射破坏单例;
import java.lang.reflect.Constructor;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_改进反射破解单例设计模式 {
    public static void main(String[] args) throws Exception{
        // 获取任意修饰符修饰的构造方法
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        // 设置强制访问
        constructor.setAccessible(true);
        Singleton s1 = constructor.newInstance();           // 抛出异常,不允许通过构造方法来创建对象
        Singleton s2= constructor.newInstance();
        System.out.println(s1 == s2);
    }
}

4.6 序列化与单例设计模式

我们单例对象创建好后,有时候需要将对象序列化到磁盘,这样就可以在文件系统中存储它的状态。反序列化的对象会重新分配内存,即被重新创建。这样则违背了单例模式的初衷,相当于破坏了单例设计模式;

4.6.1 反序列化破坏单例设计模式

  • 静态内部类方式的单例类:
package com.pattern.demo08_序列化破坏单例设计模式;
import java.io.Serializable;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton implements Serializable {        // 实现Serializable接口
    // 私有化成员
    private static Singleton singleton = null;
    // 私有化构造方法
    private Singleton() {
        System.out.println("执行了几次构造方法????");
        if (Holder.INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");            // 如果通过构造方法来创建对象则抛出异常
        }
    }
    // 共有访问方法
    public static Singleton getSingleton() {
        return Holder.INSTANCE;
    }
    // 静态内部类
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
  • 测试类:
package com.pattern.demo08_序列化破坏单例设计模式;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        try (
                // 对象输出流
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
                // 对象输入流
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
        ) {
            // 获取单例对象
            Singleton s1 = Singleton.getSingleton();
            // 写出对象
            oos.writeObject(s1);
            // 反序列化底层并不是空参执行构造方法,因此Singleton对象被创建了两次
            Singleton s2 = (Singleton) ois.readObject();
            System.out.println(s1 == s2);               // false,违反单例设计模式
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.6.2 解决反序列化破坏单例设计模式

从之前运行的结果可以发现,反序列化后的对象与之前序列化到文件中的对象并不是同一个,Singleton对象被实例化了两次,;我们如何保证在序列化的情况下也能实现单例设计模式呢?我们可以在单例对象中添加一个readResolve()方法,该方法的返回值是对象被反序列化时返回值;

  • 修改单例类:
package com.pattern.demo09_解决反序列化破坏单例;
import java.io.Serializable;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Singleton implements Serializable {        // 实现Serializable接口
    // 私有化成员
    private static Singleton singleton = null;
    // 私有化构造方法
    private Singleton() {
        System.out.println("执行了几次构造方法????");
        if (Holder.INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");            // 如果通过构造方法来创建对象则抛出异常
        }
    }
    // 共有访问方法
    public static Singleton getSingleton() {
        return Holder.INSTANCE;
    }
    // 静态内部类
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    // 该对象被反序列化时返回的对象
    private Object readResolve() {
        return Holder.INSTANCE;
    }
}
  • 再次运行反序列化测试代码,发现能保证单例;

4.6.3 探究反序列化源码

为什么在对象里面添加了readResolve()方法就可以保证对象的单例呢?这得从序列化的源码开始说起....

  • 1)我们点开ObjectInputStream类的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();
        }
    }
}
  • 2)点开readObject0()方法:
private Object readObject0(boolean unshared) throws IOException {
    ... 代码省略
        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:
                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);
                ... 代码省略
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}
  • 3)打开readOrdinaryObject方法:
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;
}

4)hasReadResolveMethod源码:

boolean hasReadResolveMethod() {
    requireInitialized();
    return (readResolveMethod != null);
}

简单的判断一下readResolveMethod变量是否为空,那么readResolveMethod是在哪里被赋值的呢?

5)通过查看源码得知,readResolveMethod在ObjectStreamClass的自由构造方法中被赋值:

readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

6)getInheritableMethod源码如下:

private static Method getInheritableMethod(Class<?> cl, String name,
                                           Class<?>[] argTypes,
                                           Class<?> returnType)
{
    Method meth = null;
    Class<?> defCl = cl;
    while (defCl != null) {
        try {
            meth = defCl.getDeclaredMethod(name, argTypes);
            break;
        } catch (NoSuchMethodException ex) {
            defCl = defCl.getSuperclass();
        }
    }
    if ((meth == null) || (meth.getReturnType() != returnType)) {
        return null;
    }
    meth.setAccessible(true);
    int mods = meth.getModifiers();
    if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
        return null;
    } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
        return meth;
    } else if ((mods & Modifier.PRIVATE) != 0) {
        return (cl == defCl) ? meth : null;
    } else {
        return packageEquals(cl, defCl) ? meth : null;
    }
}

上面代码的逻辑其实就是通过反射找到一个readResolve方法,之后保存下来;赋值给readResolveMethod

7)再回到ObjectInputStream的readOrdinaryObject方法:

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);       // 最终返回的是desc.invokeReadResolve方法的返回值
        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;
}

8)desc.invokeReadResolve源码如下:

Object invokeReadResolve(Object obj)
    throws IOException, UnsupportedOperationException
{
    requireInitialized();
    if (readResolveMethod != null) {
        try {
            return readResolveMethod.invoke(obj, (Object[]) null);    // 将readResolveMethod封装的方法执行
        } 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();
    }
}

这个方法的逻辑就是将readResolveMethod封装的方法执行,readResolveMethod封装的方法正是我们之前查看的readResolve方法;也就是说,最终反序列化返回的对象是这个对象readResolve方法的返回值;

4.7 枚举保证单例设计模式

4.7.1 设计枚举单例类

  • 枚举单例类:
package com.pattern.demo10_枚举设计单例;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum Singleton {
    INSTANCE;
    public static Singleton getSingleton() {
        return INSTANCE;
    }
}
  • 测试枚举单例类:
package com.pattern.demo10_枚举设计单例;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) throws Exception {
        Singleton s1 = Singleton.INSTANCE;
        // 测试反射
        Class<Singleton> clazz = Singleton.class;
//        Singleton s3 = clazz.newInstance();         // 出现异常: java.lang.NoSuchMethodException
        // 测试序列化
        try (
                // 对象输出流
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
                // 对象输入流
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
        ) {
            // 写出对象
            oos.writeObject(s1);
            // 反序列化
            Singleton s2 = (Singleton) ois.readObject();
            System.out.println(s1 == s2);               // true,枚举能够保证对象在反序列化时的一致
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.7.2 为什么枚举单例对象不会被破坏?

  • 1)探究为什么反射不会破坏枚举单例对象:

在Enum类中定义着如下的构造方法:

protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

意味着所有的枚举类都会有一个这样的构造方法(空参构造方法将会被擦除)

我们尝试反射其有参构造方法:

package com.pattern.demo10_枚举设计单例;
import java.lang.reflect.Constructor;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_探究反射破坏枚举单例对象 {
    public static void main(String[] args) throws Exception{
        // 获取指定的构造器
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
        // 设置强制访问
        constructor.setAccessible(true);
        // 创建对象
        Singleton singleton = constructor.newInstance("xxx", 110);
    }
}

运行程序出现如下错误:

Tips:在Java的反射机制中,不允许对枚举类进行反射;

  • 2)探究为什么反序列化不会破坏枚举单例对象?

关于反序列化的代码都在ObjectInputStream类的readEnum方法中:

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;
}

通过标注的核心代码可知,枚举类型的反序列化实质上就是通过Enum类的valueOf方法获取枚举类的唯一实例;

相关文章
|
12天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
17天前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
22 2
|
22天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
29 4
|
2月前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
49 0
[Java]23种设计模式
|
27天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
2月前
|
设计模式 监控 算法
Java设计模式梳理:行为型模式(策略,观察者等)
本文详细介绍了Java设计模式中的行为型模式,包括策略模式、观察者模式、责任链模式、模板方法模式和状态模式。通过具体示例代码,深入浅出地讲解了每种模式的应用场景与实现方式。例如,策略模式通过定义一系列算法让客户端在运行时选择所需算法;观察者模式则让多个观察者对象同时监听某一个主题对象,实现松耦合的消息传递机制。此外,还探讨了这些模式与实际开发中的联系,帮助读者更好地理解和应用设计模式,提升代码质量。
Java设计模式梳理:行为型模式(策略,观察者等)
|
2月前
|
Java 数据库连接
深入探索研究Java中的异常处理机制
【10月更文挑战第8天】
15 0
|
2月前
|
设计模式 Java
Java设计模式
Java设计模式
32 0
|
2月前
|
设计模式 Java
Java设计模式之外观模式
这篇文章详细解释了Java设计模式之外观模式的原理及其应用场景,并通过具体代码示例展示了如何通过外观模式简化子系统的使用。
32 0
|
2月前
|
设计模式 Java
Java设计模式之桥接模式
这篇文章介绍了Java设计模式中的桥接模式,包括桥接模式的目的、实现方式,并通过具体代码示例展示了如何分离抽象与实现,使得两者可以独立变化。
46 0