java设计模式之单例模式|单例模式之饿汉模式、懒汉模式、枚举方式|最详细的6种懒汉模式详解

2022-10-06 14:05:03 浏览数 (1)

目录

一、单例模式

二、饿汉模式和懒汉模式

1、饿汉模式,线程安全

2、懒汉模式

懒汉模式1,线程不安全(不常用)

懒汉模式2,线程安全(不常用)

懒汉模式3,线程安全,双重校验(不常用)

懒汉模式4,线程安全,双重校验,volatile可见性,实现较为复杂

懒汉模式5,线程安全,静态内部类

懒汉模式6,线程安全,静态内部类,防止反射

3、readResolve方法

序列化测试

ObjectOutputStream是怎么校验readResolve()的

概括一下ObjectOutputStream().readObject()的整个大致流程

4、枚举方式,线程安全(不常用)

三、项目地址


一、单例模式

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

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

1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。

单例模式的特点总结就是

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

二、饿汉模式和懒汉模式

饿汉式和懒汉式的区别,就是懒汉式比较懒,不先加载实例;饿汉式不管用户是否要使用该类的对象,就先创建好了一个实例放在内存中。

1、饿汉模式,线程安全

对象实例创建

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.hungryMode;

/**
 * 懒汉模式
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class Singleton {
    /* 持有私有静态实例,防止被引用*/
    private static Singleton instance = new Singleton();

    /* 私有构造方法,防止被实例化 */
    private Singleton() {
    }

    /* 静态工程方法,返回Singleton实例 */
    public static Singleton getInstance() {
        return instance;                                                                   // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return instance;
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

test调用Singleton实例中的方法

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.hungryMode;

/**
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonTest {
    public static void main(String[] args) {
        //不合法的构造函数
        //编译时错误:构造函数 Singleton() 是不可见的
        //Singleton instance = new Singleton();                              // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410

        //获取唯一可用的对象
        Singleton instance = Singleton.getInstance();

        //调用方法
        instance.dosomething();
    }
}

执行结果 

上述方法就是实现单例模式的其中一种(饿汉模式),这种方式比较常用,但是在类中不管用户是否要使用该类的对象,就先创建好了一个实例放在内存中,这就比较浪费内存。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

它基于 classloader 机制避免了多线程的同步问题

我们试着不先创建对象的实例,到用的时候才去创建,这就需要用到懒汉模式

2、懒汉模式

懒汉模式1,线程不安全(不常用)

对象实例创建

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy1;

/**
 * 懒汉模式1,线程不安全
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy1 {
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static SingletonLazy1 instance = null;

    /* 私有构造方法,防止被实例化 */
    private SingletonLazy1() {
    }

    /* 静态工程方法,创建实例 */
    public static SingletonLazy1 getInstance() {
        if (instance == null) {
            instance = new SingletonLazy1();
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return instance;
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

test调用对象实例中的方法

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy1;

import cn.zygxsq.design.module.singletonPattern.hungryMode.Singleton;

/**
 * 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonTestLazy1 {
    public static void main(String[] args) {
        //不合法的构造函数
        //编译时错误:构造函数 SingletonLazy1() 是不可见的
        //SingletonLazy1 instance = new SingletonLazy1();

        //获取唯一可用的对象
        SingletonLazy1 instance = SingletonLazy1.getInstance();                                            // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410

        //调用方法
        instance.dosomething();
    }
}

执行结果

 上述懒汉模式可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字

懒汉模式2,线程安全(不常用)

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy2;

/**
 * 懒汉模式1,线程安全
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy2 {
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static SingletonLazy2 instance = null;

    /* 私有构造方法,防止被实例化 */
    private SingletonLazy2() {
    }

    /*  synchronized加锁,保证单例 */
    public static synchronized SingletonLazy2 getInstance() {
        if (instance == null) {
            instance = new SingletonLazy2();                                                      // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return instance;
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

 上述实现方式可以保证线程安全了,但是,synchronized作为修饰符在方法上使用,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,需要改进

懒汉模式3,线程安全,双重校验(不常用)

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy3;

/**
 * 懒汉模式1,线程安全
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy3 {
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static SingletonLazy3 instance = null;

    /* 私有构造方法,防止被实例化 */
    private SingletonLazy3() {
    }

    /*  synchronized加锁,保证单例 */
    public static SingletonLazy3 getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy3.class) {
                if (instance == null) {
                    instance = new SingletonLazy3();
                }
            }
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return instance;
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

 上述实现方式似乎解决了之前提到的问题,将synchronized关键字加在了方法内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的。会出现指令重排序的情况

看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说

instance = new SingletonLazy3(); 这一段代码

语句并非是一个原子操作,在 JVM 中这句代码大概做了下面 3 件事情:

1、给 new的对象 分配内存

2、调用 Singleton 的构造函数来初始化成员变量

3、将引用instance指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,另外一个线程B抢夺到了CPU的执行权,这时instance已经是非null了(但却没有初始化),所以线程B会直接返回 instance,然后使用,结果就会出现问题了(因为对象还没有初始化)。

所以对于第三种进行优化的方式,就是对instance加一个volatile可见性 ,防止指令重排序

private volatile static SingletonLazy4 instance = null;

懒汉模式4,线程安全,双重校验,volatile可见性,实现较为复杂

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy4;

/**
 * 懒汉模式4,线程安全,双重校验,volatile可见性,实现较为复杂
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy4 {
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private volatile static SingletonLazy4 instance = null;

    /* 私有构造方法,防止被实例化 */
    private SingletonLazy4() {
    }

    /*  synchronized加锁,保证单例 */
    public static SingletonLazy4 getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy4.class) {
                if (instance == null) {
                    instance = new SingletonLazy4();                                               // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
                }
            }
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return instance;
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

 懒汉模式4基本可以用了,但是实现起来比较繁琐,还可以有另外一种简单的方式,使用内部类来实现。(原创文章原文链接)

懒汉模式5,线程安全,静态内部类

使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的(就是加载完毕后别的线程才能使用)。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5;

/**
 * 懒汉模式5,线程安全,静态内部类
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy5 {
    /* 私有构造方法,防止被实例化 */
    private SingletonLazy5() {
    }

    /* 此处使用一个内部类来维护单例 */
    private static class SingletonFactory {
        private static SingletonLazy5 instance = new SingletonLazy5();
    }

    /* 获取实例 */
    public static SingletonLazy5 getInstance() {
        return SingletonFactory.instance;                                             // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return getInstance();
    }

/* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

这种懒汉模式静态内部类方式和饿汉模式很像,可以和饿汉模式对比着看一下,饿汉模式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到懒加载效果),而这种懒汉方式5静态内部类方式是 SingletonLazy5 类被装载了,instance 不一定被初始化。因为 SingletonFactory 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonFactory 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉模式方式就显得很合理。

懒汉模式5静态内部类方式已经很牛了,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

还有,懒汉模式5静态内部类已经很牛了,但是如果遇到反射调用,我们可以使用反射去创建这个类的对象,即使它的构造器是私有的,我们也是可以调用到的,那也可以创建多个实例。那么这个时候我们就需要再次修改代码去访问别人反射调用构造器。

所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。 

反射调用demo

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * 反射获取私有构造函数,创建多个实例
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonTestLazy5Reflection {
    public static void main(String[] args) throws Exception{

        // 1:通过Class的静态方法forName加载
        Class aClass = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5.SingletonLazy5");

        //获取私有构造方法
        //Constructor con = c.getConstructor(String.class);
        //NoSuchMethodException没有这个方法异常
        //原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以
        Constructor con = aClass.getDeclaredConstructor();

        //用该私有方法创建对象
        //IllegalAccessException:非法访问异常
        //暴力访问
        con.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查

        // 实例化对象的方法
        Object o1 = con.newInstance();
        System.out.println(o1);
        //Method m = o1.getClass().getDeclaredMethod("dosomething", null);

        //访问方法
        Method m = aClass.getDeclaredMethod("dosomething", null);

       //调用方法
        m.invoke(o1, null);

        // 第二次创建对象
        Class aClass2 = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy5.SingletonLazy5");
        Constructor con2 = aClass2.getDeclaredConstructor();

        //用该私有方法创建对象
        //IllegalAccessException:非法访问异常
        //暴力访问
        con2.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查

        // 实例化对象的方法
        Object o2 = con2.newInstance();
        System.out.println(o2);
    }
}

执行结果 

 可以看到多个对象是不同的

懒汉模式6,线程安全,静态内部类,防止反射

我们为了避免别人反射调用,我们修改一下构造器为下面这样的

private SingletonLazy6() { if(!flag){ flag = true; }else{ throw new RuntimeException("不能多次创建单例对象"); } }

 demo

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6;

/**
 * 懒汉模式6,线程安全,静态内部类,防止反射多次
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy6 {
    private static boolean flag;

    /* 私有构造方法,防止被实例化 */
    private SingletonLazy6() {
        if(!flag){
            flag = true;
        }else{
            throw new RuntimeException("不能多次创建单例对象");
        }
    }

    /* 此处使用一个内部类来维护单例 */
    private static class SingletonFactory {
        private static SingletonLazy6 instance = new SingletonLazy6();
    }

    /* 获取实例 */
    public static SingletonLazy6 getInstance() {
        return SingletonFactory.instance;                                                                   // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return getInstance();
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

test反射多次调用测试

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * 反射获取私有构造函数,创建多个实例
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonTestLazy6Reflection {
    public static void main(String[] args) throws Exception{

        // 1:通过Class的静态方法forName加载
        Class aClass = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.SingletonLazy6");

        //获取私有构造方法
        //Constructor con = c.getConstructor(String.class);                                                             // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
        //NoSuchMethodException没有这个方法异常
        //原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以
        Constructor con = aClass.getDeclaredConstructor();

        //用该私有方法创建对象
        //IllegalAccessException:非法访问异常
        //暴力访问
        con.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查

        // 实例化对象的方法
        Object o1 = con.newInstance();
        System.out.println(o1);
        //Method m = o1.getClass().getDeclaredMethod("dosomething", null);

        //访问方法
        Method m = aClass.getDeclaredMethod("dosomething", null);

       //调用方法
        m.invoke(o1, null);

        // 第二次创建对象
        Class aClass2 = Class.forName("cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.SingletonLazy6");
        Constructor con2 = aClass2.getDeclaredConstructor();

        //用该私有方法创建对象
        //IllegalAccessException:非法访问异常
        //暴力访问
        con2.setAccessible(true);//值为true则指示反射的对象在使用是应该取消Java语言访问检查

        // 实例化对象的方法
        Object o2 = con2.newInstance();
        System.out.println(o2);

    }
}

执行结果

3、readResolve方法

 懒汉模式6静态内部类防止反射,反射的问题处理完了之后,这里还有一个问题,就是如果把单例对象进行序列化然后再反序列化,那么内存中就会出现俩个一样的单例对象,只是内存地址不同。这种情况我们可以使用readResolve方法来防止。

private Object readResolve(){.....} 

ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出ClassCastException 。

序列化测试

实现一下Serializable 

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.testReadResolve;

import java.io.Serializable;

/**
 * 懒汉模式6,线程安全,静态内部类,防止反射多次
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonLazy6Serializable implements Serializable {
    private static boolean flag;

    /* 私有构造方法,防止被实例化 */
    private SingletonLazy6Serializable() {
        if(!flag){
            flag = true;
        }else{
            throw new RuntimeException("不能多次创建单例对象");
        }
    }

    /* 此处使用一个内部类来维护单例 */
    private static class SingletonFactory {
        private static SingletonLazy6Serializable instance = new SingletonLazy6Serializable();
    }

    /* 获取实例 */
    public static SingletonLazy6Serializable getInstance() {
        return SingletonFactory.instance;                                                              // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    private Object readResolve() {
        return getInstance();
    }

    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");
    }
}

test调用SingletonLazy6Serializable 

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.testReadResolve;

import cn.zygxsq.design.module.singletonPattern.lazyMode.lazy6.SingletonLazy6;

import java.io.*;

/**
 * 反射获取私有构造函数,创建多个实例
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/4.
 */
public class SingletonTestLazy6ReadResolve {
    public static void main(String[] args) throws Exception{

        // 将SingletonLazy6Serializable类的readResolve()方法注释执行一下和不注释执行一下
        // 查看打印结果
        SingletonTestLazy6ReadResolve singletonTestLazy6ReadResolve = new SingletonTestLazy6ReadResolve();
        singletonTestLazy6ReadResolve.copy();
    }

    //测试方式,把单例对象序列化后再反序列化从而获得一个新的对象 就相当于复制了一个单例对象
    public SingletonLazy6Serializable copy() throws Exception{
        System.out.println(SingletonLazy6Serializable.getInstance());
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(SingletonLazy6Serializable.getInstance());                                                                     // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410

        InputStream is = new ByteArrayInputStream(os.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(is);
        SingletonLazy6Serializable obj = (SingletonLazy6Serializable) ois.readObject();
        System.out.println(obj);
        return obj;
    }
}

 将SingletonLazy6Serializable类的readResolve()方法注释执行一下和不注释执行一下,分别查看一下打印结果

注释readResolve()方法执行结果,返回的是内存地址不同

 不注释readResolve()方法执行结果,返回的内存地址相同

 这就是为什么我将单例模式都要加一个readResolve()方法了,这个你们在其他博客基本上是很难见到的,我这里(小小鱼儿小小林)讲得稍微更详细更清楚一点,其他人估计只能讲到上述的饿汉模式和懒汉模式5静态内部类,基本上已经很全很厉害了,其实懒汉模式5静态内部类基本上也够用了。能解决系统的97%了

我这里额外加了一个懒汉模式6静态内部类防止反射,并且讲述了必须加一个readResolve()方法,不然会有序列化问题。 

ObjectOutputStream是怎么校验readResolve()的

可以看一下readObject()方法

 再看一下

Object obj = readObject0(false);

 看一下readOrdinaryObject()方法,很重要

case TC_OBJECT:                     return checkResolve(readOrdinaryObject(unshared));

代码语言:javascript复制
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;
    }

看一下下面的截图

readOrdinaryObject(boolean unshared)方法中获取单例类的ObjectStreamClass对象desc,判断对象是否能实例化。可以则进行实例化,至此单例类进行了第一次实例化,对象名为obj

 第一次实例化完成后,通过反射寻找该单例类中的readResolve()方法,没有则直接返回obj对象。这就是我们对没有readResolve()方法的类进行序列化后生成不同对象的原因。 因为我们有定义readResolve()方法,desc通过invokeReadResolve(Object obj)方法调用readResolve()方法获取单例对象instance,将他赋值给rep

概括一下ObjectOutputStream().readObject()的整个大致流程

代码语言:javascript复制
1.我们在单例类中定义一个readResolve()方法,用于返回instance对象。
2.反序列化获取单例类对象时调用readObject()方法。
3.readObject()方法中调用readObject0()方法。
4.readObject0()方法中调用readOrdinaryObject(boolean unshared)方法。
5.readOrdinaryObject(boolean unshared)方法中获取单例类的ObjectStreamClass对象desc,判断对象是否能实例化。可以则进行实例化,至此单例类进行了第一次实例化,对象名为obj。
6.第一次实例化完成后,通过反射寻找该单例类中的readResolve()方法,没有则直接返回obj对象。这就是我们对没有readResolve()方法的类进行序列化后生成不同对象的原因。
7.因为我们有定义readResolve()方法,desc通过invokeReadResolve(Object obj)方法调用readResolve()方法获取单例对象instance,将他赋值给rep。
8.rep与obj进行比较,由于obj是反射获取的对象,当然与rep不等,于是将rep的值instance赋值给obj,将obj返回,返回对象instance也就保证了单例。
9.简而言之就是当我们通过反序列化readObject()方法获取对象时会去寻找readResolve()方法,如果该方法不存在则直接返回新对象,如果该方法存在则按该方法的内容返回对象。

懒汉模式6静态内部类防止反射写起来还是有点复杂的,其实还有一种更简单的方式,那就是用枚举的方式

4、枚举方式,线程安全(不常用)

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.enumMode;

/**
 * 单例模式,枚举方式
* 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
 * Created by yjl on 2022/8/5.
 */
public enum  SingletonEnum {
    INSTANCE;


    /* 要操作的一些方法*/
    public void dosomething(){
        System.out.println("单例模式方法调用");                                                                        // 原文:https://blog.csdn.net/qq_27471405/article/details/127167410
    }
}

调用的话就跟枚举方式调用的方式一样

代码语言:javascript复制
package cn.zygxsq.design.module.singletonPattern.enumMode;

/**
 * Created by yjl on 2022/8/5.
 */
public class SingletonEnumTest {
    public static void main(String[] args) {
        SingletonEnum instance = SingletonEnum.INSTANCE;
        instance.dosomething();
    }
}

 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

不能通过 reflection attack 来调用私有构造方法。

三、项目地址

以上代码在下面地址中有全部代码

https://github.com/jalenFish/design-patterns/


参考文章:

单例模式 | 菜鸟教程

单例设计模式之readResolve()方法_♀桂圆的博客-CSDN博客

感谢原作者的分享,让技术人能够更快的解决问题

0 人点赞