重学设计模式 | 单例模式(Singleton Pattern)(下)

简介: 重学设计模式 | 单例模式(Singleton Pattern)

① 如何保证线程安全


直接在idea上打开生成的SingletonEnum.class文件:


网络异常,图片无法展示
|


好吧,没看到有用的信息,再用JDK自带反编译工具javap编译下:


网络异常,图片无法展示
|


可以看到继承自 Enum类,但是代码不够全,再用jad工具反编译下:


网络异常,图片无法展示
|


反编译后的代码如下:


import java.util.concurrent.atomic.AtomicLong;
public final class SingletonEnum extends Enum {
    public static SingletonEnum[] values() {
        return (SingletonEnum[]) $VALUES.clone();
    }
    public static SingletonEnum valueOf(String name) {
        return (SingletonEnum) Enum.valueOf(SingletonEnum, name);
    }
    private SingletonEnum(String s, int i) {
        super(s, i);
    }
    public long getId() {
        return id.incrementAndGet();
    }
    public static final SingletonEnum INSTANCE;
    private final AtomicLong id = new AtomicLong(0L);
    private static final SingletonEnum $VALUES[];
    static {
        INSTANCE = new SingletonEnum("INSTANCE", 0);
        $VALUES = (new SingletonEnum[]{
                INSTANCE
        });
    }
}


可以看到**INSTANCE的初始化发生在static静态代码块**中,即在类加载阶段执行,保证了线程安全,但跟饿汉式一样,没有懒加载。


② 如何保证克隆安全


而防克隆则是要来到父类**Enum类**中,直接实现了clone()函数:


网络异常,图片无法展示
|


调用此函数直接返回 CloneNotSupportedException 异常。


③ 如何保证反射安全


将反射部分代码中的Singleton改成SingletonEnum,接着运行下,抛出下述异常


网络异常,图片无法展示
|


在获取构造函数时抛出的异常,没有此构造方法,呕吼,看回jad反编译的代码:


网络异常,图片无法展示
|


这里使用的不是无参构造方法,而是有两个参数,改下反射代码,往getDeclaredConstructor()传入这个两个参数:


网络异常,图片无法展示
|


再次运行,还是报异常:


网络异常,图片无法展示
|


定位到 Constructor类newInstance()


网络异常,图片无法展示
|


反射通过newInstance()创建对象时,会检查该类是否**ENUM**修饰,是则抛出异常,反射失败。


④ 如何保证序列化安全


Java规范中规定:每一个枚举类型及其定义的枚举变量在JVM中都是唯一的


因此在枚举类型的序列化和反序列化上,Java做了特殊的规定:


  • 序列化时 → 仅仅将枚举对象的name属性输出到结果中;


  • 反序列化时 → 通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。


定位到Enum类的valueOf()方法:


网络异常,图片无法展示
|


调用enumType(Class对象的引用)的enumConstantDirectory获取一个Map集合,集合中存放的键值对:


枚举name : 枚举示例变量


网络异常,图片无法展示
|


根据name即可拿到枚举实例,所以枚举单例序列化并不会重新创建新实例!


0x5、Kotlin中的单例


说完Java的单例,顺带提提Kotlin中的单例,使用一行代码即可创建安全单例:


object KotlinSingleton


就是这么简短,依次点击 ToolsKotlinShow Kotlin BytecodeDecompile 反编译下:


网络异常,图片无法展示
|


呕吼,static静态代码块,饿汉式变种,线程安全,Android项目不考虑其他三个问题的话,可以大胆放心使用。


0x6、如何实现一个多例


单例 指的是:一个类只能创建一个对象,对应的 多例 则是:


一个类可以创建多个对象,但个数是有限的,比如只能创建5个对象。


实现方式也比较简单,通过一个Map来存储对象类型及对象间的对应关系,来控制对象的个数。示例如下:


import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class BikeServer {
    private final long bikeNo;    // 共享单车编号
    private final String address; // 共享单车地址
    private static final int BIKE_COUNT = 5;    // 单车数量
    private static final Map<Long, BikeServer> bikeInstances = new HashMap<>(); // 单车实例集合
    // 私有化构造方法
    private BikeServer(long bikeNo, String address) {
        this.bikeNo = bikeNo;
        this.address = address;
    }
    // 静态代码块中初始化实例
    static {
        bikeInstances.put(1L, new BikeServer(1L, "罗湖区"));
        bikeInstances.put(2L, new BikeServer(2L, "南山区"));
        bikeInstances.put(3L, new BikeServer(3L, "福田区"));
        bikeInstances.put(4L, new BikeServer(4L, "宝安区"));
        bikeInstances.put(5L, new BikeServer(5L, "龙华区"));
    }
    // 根据编号获取单车实例
    public static BikeServer getInstance(long bikeNo) {
        return bikeInstances.get(bikeNo);
    }
    // 随机获取单车实例
    public static BikeServer getRandomInstance() {
        Random r = new Random();
        return bikeInstances.get(r.nextInt(BIKE_COUNT) + 1L);
    }
    @Override
    public String toString() {
        return "BikeServer{" +
                "bikeNo=" + bikeNo +
                ", address='" + address + '\'' +
                '}';
    }
}


调用下:


public class BikeTest {
    public static void main(String[] args) {
        System.out.println(BikeServer.getInstance(2L).toString());
        System.out.println(BikeServer.getRandomInstance().toString());
    }
}
// 输出结果:
BikeServer{bikeNo=2, address='南山区'}
BikeServer{bikeNo=5, address='龙华区'}


注:本节讨论的单例都是进程内唯一,进程间不唯一,即不适用于多进程(集群)单例。


参考文献:




相关文章
|
7天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
13 2
|
21天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
29 4
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
13天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
1月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
19 1
|
21天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
23 0
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
25 0
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###