设计模式:深入研究单例设计模式(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方法获取枚举类的唯一实例;

相关文章
|
3天前
|
设计模式 算法 Java
Java编程中的设计模式:简化复杂性的艺术
在Java的世界中,设计模式如同一位智慧的导师,指引着开发者们在复杂的编码迷宫中找到出口。本文将深入浅出地探讨几种常见的设计模式,通过实例演示如何在Java项目实践中运用这些模式,从而提升代码的可维护性和扩展性。无论你是新手还是资深开发者,这篇文章都将为你打开一扇通往高效编码的大门。
12 1
|
5天前
|
设计模式 人工智能 Java
Java 如何使用单例类
Java 如何使用单例类
5 1
|
6天前
|
设计模式 安全 Java
|
9天前
|
设计模式 算法 Java
Java中的设计模式:探索与实践
【8月更文挑战第10天】在Java开发中,设计模式是提升代码可读性、可维护性和扩展性的关键所在。本文将深入探讨几种常见的设计模式及其在实际项目中的应用,帮助开发者更好地理解和运用这些模式,以编写出更高质量的代码。
25 2
|
3天前
|
设计模式 存储 Java
掌握Java设计模式的23种武器(全):深入解析与实战示例
掌握Java设计模式的23种武器(全):深入解析与实战示例
|
5天前
|
设计模式 安全 Java
怎样才能学好 Java 设计模式?
本文探讨了在软件开发中对设计模式的常见误解。许多人认为设计模式过于抽象难以学习,或是应用场景有限难以在复杂的业务环境中应用,甚至有人误以为所有问题都能通过设计模式解决。实际上,设计模式旨在解决特定范围内的面向对象设计问题,如提高代码的可复用性。为了正确理解和应用设计模式,需要摆正心态、深入了解其背景知识、培养独立思考的习惯,并坚持不懈地学习和实践。通过这些方法,开发者可以更好地掌握设计模式,从而在实际项目中做出更加合理的设计决策。
|
5天前
|
设计模式 Java
【Java】单例设计模式
【Java】单例设计模式
|
11天前
|
设计模式 存储 安全
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
40 0
|
1月前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
【7月更文挑战第12天】在本文中,作者宏哥介绍了如何在不使用PageFactory的情况下,用Java和Selenium实现Page Object Model (POM)。文章通过一个百度首页登录的实战例子来说明。首先,创建了一个名为`BaiduHomePage1`的页面对象类,其中包含了页面元素的定位和相关操作方法。接着,创建了测试类`TestWithPOM1`,在测试类中初始化WebDriver,设置驱动路径,最大化窗口,并调用页面对象类的方法进行登录操作。这样,测试脚本保持简洁,遵循了POM模式的高可读性和可维护性原则。
27 2
|
17天前
|
设计模式 SQL 安全
单例模式大全:细说七种线程安全的Java单例实现,及数种打破单例的手段!
设计模式,这是编程中的灵魂,用好不同的设计模式,能使你的代码更优雅/健壮、维护性更强、灵活性更高,而众多设计模式中最出名、最广为人知的就是Singleton Pattern单例模式。通过单例模式,我们就可以避免由于多个实例的创建和销毁带来的额外开销,本文就来一起聊聊单例模式。