首页 > 其他分享 >设计模式——单例模式

设计模式——单例模式

时间:2025-01-12 19:58:52浏览次数:3  
标签:加载 模式 枚举 线程 User 单例 设计模式 public user

单例模式


单例模式(Singleton Pattern)是软件工程中的一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

实现单例模式的方法

前置条件

创建一个User类,模拟单例模式中创建对象使用。

public class User {
    private Integer id;
    private String name;
    private String password;
    public User() {
    }

    public User(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

懒汉式(Lazy Initialization)

单例模式中常见的模式之一,懒汉式可以做到使用单例对象时才创建对象,可以实现延迟加载,但是存在线程安全问题,需要通过synchronized关键字保证了线程安全,但会影响性能。

/*懒汉式 线程不安全 需要使用 synchronized*/
public class Lazy {

    private static User user;

    //没有synchronized关键字,线程不安全,多线程调用此方法时会创建不同地址值的User对象
    //对外提供接口
    public static synchronized User getUser() {
        if (user == null) {
            user = new User(1,"zhao","123456");
        }
        return user;
    }


    public static void main(String[] args) {
        //测试多线程下的懒汉式
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                User user = getUser();
                System.out.println(user);
            }
            ).start();
        }
    }
}

若没有添加synchronized关键字执行结果为:

会出现创建出多个对象的情况,背离了单例模式的初初衷。

添加synchronized关键字后可以保障对象的唯一性
在这里插入图片描述

饿汉式(Eager Initialization)

单例模式中常见的模式之一,饿汉式是在类加载时就创建实例比较简单,可以保证线程安全,但不支持延迟加载。

/*
* 饿汉式 线程安全
* */
public class Hungry {
    //类加载时就创建实例
    private static final User user = new User(1,"zhao","123456");
    //对外提供接口
    public static User getUser(){
        return user;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                User user = getUser();
                user.setId(2);
                System.out.println(user);
            }
            ).start();
        }
    }
}

运行结果:
在这里插入图片描述
实现了单例模式

双重锁式(Double-Checked Locking)

双锁结构,优化懒汉式,为了提高性能同时保持线程安全性,可以采用双重检查锁定的方式。

volatile关键字: 是一种轻量级的同步机制,适用于那些不需要复杂同步逻辑的简单场景。如果应用需要更复杂的并发控制,那么应该考虑使用更高级的同步工具
优点:
1、可见性:
当一个线程修改了 volatile 变量的值,这个修改会立即对其他线程可见。
每次读取 volatile 变量时,都会直接从主内存中读取最新的值,而不是使用缓存中的旧值。
每次写入 volatile 变量时,都会立即将更新后的值写回主内存,确保其他线程可以看到最新的状态。
2、禁止指令重排序:
Java 编译器和处理器为了优化性能,可能会对指令进行重排序。然而,对于 volatile 变量的操作,编译器和运行时环境都必须遵守一定的规则,不能将 volatile 写操作放到读操作之后,也不能将 volatile 读操作放到写操作之前。这有助于维持程序的逻辑正确性。
缺点:
1、不保证原子性:
尽管 volatile 提供了可见性和有序性,但它并不提供原子性。这意味着如果对 volatile 变量执行复合操作(如 i++),这些操作仍然可能受到竞态条件的影响,因为它们不是原子性的。要确保原子性,可以考虑使用同步机制、Atomic 类或锁等方法。

synchronized 关键字: 是 Java 中用于实现线程同步的关键字,它能够确保在多线程环境中对共享资源的安全访问。

/*
双锁结构,优化懒汉式,为了提高性能同时保持线程安全性,可以采用双重检查锁定的方式。
 */
public class DoubleChecked {
    // 使用volatile避免指令重排序,保证user的可见性
    private static volatile User user;

    //对外提供接口
    public static User getUser() {
        if (user == null) {
            synchronized (User.class) {
                if (user == null) {
                    user = new User(1, "zhao", "123456");
                }
            }
        }
        return user;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                User user = getUser();
                System.out.println(user);
            }
            ).start();
        }
    }
}

运行结果:
在这里插入图片描述

静态内部类式(Static Inner Class)

利用了Java语言的特性,只有当内部类被加载时才会创建单例实例,因此它是线程安全且延迟加载的。
静态内部类: 只有在它被显式使用时才会被加载,比如创建静态内部类的实例或者访问其静态成员。

/*
* 内部类 利用了Java语言的特性,只有当内部类被加载时才会创建单例实例,因此它是线程安全且延迟加载的。
*/
public class StaticInnerClass {

    //创建内部类:只有当内部类被加载时才会创建单例实例
    private static class GetUserInnerClass{
        private static final User user = new User(1,"zhao","123456");
    }

    //对外提供接口
    public static User getUser(){
        return GetUserInnerClass.user;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                User user = getUser();
                System.out.println(user);
            }
            ).start();
        }
    }
}

运行结果:
在这里插入图片描述

枚举式(Enum)

枚举类型的单例模式之所以线程安全,主要是因为Java语言规范对枚举类型的初始化提供了保证,并且在多个方面限制了创建新实例的可能性。以下是具体的原因:
1. 类加载机制
Java的类加载器在加载类时是线程安全的。当一个类第一次被加载到JVM中时,类加载器会确保这个过程是原子性的,即同一时间只有一个线程可以执行类的加载和初始化。对于枚举类型来说,它的静态字段(包括枚举常量)是在类加载期间初始化的,这意味着所有枚举实例的创建都是在这个安全的过程中完成的。

2. 构造函数私有化
枚举类型的构造函数默认是私有的,这防止了外部代码通过new关键字来创建新的枚举实例。即使使用反射技术尝试调用私有构造函数,在Java 5及以上版本中,JVM也特别处理了这种情况,以确保无法通过反射创建额外的枚举实例。

3. 静态初始化块
枚举实例是通过静态初始化块创建的,而静态初始化块只会在类加载时执行一次,并且由JVM保证其线程安全性。由于枚举实例是静态成员变量的一部分,它们的初始化也是线程安全的。

4. 序列化机制
Java的序列化机制为枚举类型提供了特殊的支持。当试图反序列化一个枚举实例时,JVM不会创建一个新的对象,而是返回已经存在的枚举常量。如果有人尝试通过反序列化来创建新的枚举实例,JVM会抛出InvalidObjectException异常,从而防止了这种攻击。

5. 克隆保护
枚举类型还重写了Cloneable接口中的clone()方法,使其抛出CloneNotSupportedException异常。这阻止了通过克隆方式创建新的枚举实例。

6. 反射保护
如前所述,尽管反射可以用来访问私有构造函数或字段,但Java的反射API对枚举类型进行了特殊的处理,使得无法利用反射来创建新的枚举实例。

//枚举实现单例模式
//枚举类型本质上就是线程安全的,并且默认情况下是不可变和序列化的,非常适合用来实现单例模式。
public enum Enum {
    GETUSER;

    //枚举实现单例模式
    private User user;
    //构造方法私有化,创建user对象
    Enum() {
        user = new User(1,"zhao","123456");
    }
    //获取user对象
    public User getUser() {
        return user;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Enum e = Enum.GETUSER;
                User user = e.getUser();
                System.out.println(user);
            }
            ).start();
        }
    }
}

运行结果

在这里插入图片描述

总结

1. 饿汉式(Eager Initialization)
特点:类加载时即创建实例。
优点:线程安全,实现简单。
缺点:不支持延迟加载,可能浪费资源。
2. 懒汉式(Lazy Initialization)
特点:第一次调用 getInstance() 方法时才创建实例。
优点:支持延迟加载。
缺点:基本实现不是线程安全的;需要额外措施确保线程安全(如同步整个方法或使用双重检查锁定)。
3. 双重检查锁定(Double-Checked Locking)
特点:懒加载且线程安全,只在第一次创建实例时加锁。
优点:线程安全,支持延迟加载,性能较好。
缺点:实现稍微复杂一些。
4. 静态内部类(Static Inner Class)
特点:利用了Java语言特性,只有当静态内部类被加载时才会创建单例实例。
优点:线程安全,支持延迟加载,代码简洁。
缺点:相对不太直观。
5. 枚举(Enum)
特点:最简洁、最安全的方式,天然防止反射和序列化攻击。
优点:线程安全,支持延迟加载,代码非常简洁,防止反序列化攻击。
缺点:扩展性有限,不能继承其他类(只能实现接口)。

选择哪种方式?
1、如果你确定你的应用不会有多线程问题或者你不关心性能,那么饿汉式可能是最简单的解决方案。
2、如果你需要延迟加载并且希望保持线程安全,那么建议使用双重检查锁定、静态内部类或枚举的方式来实现懒汉式单例模式。
3、枚举类型的单例模式是实现单例模式的一个非常好的选择,特别是在你需要一种简单、线程安全并且能抵抗反射和序列化攻击的情况下。

标签:加载,模式,枚举,线程,User,单例,设计模式,public,user
From: https://blog.csdn.net/weixin_49845828/article/details/144999685

相关文章

  • Python 基础知识 之 选择(分支)结构 + 模式匹配结构(match)
    选择结构按照条件选择执行不同的代码段1.单分支结构if语法结构执行流程:如果表达式的值为True,就执行语句块,如果表达式的值为False,就跳过语句块,继续执行下面的语句⭐注意:⭐⭐⭐表达式后面的冒号;缩进,python中通过缩进来控制程序逻辑示例;#1.判断是否中奖nu......
  • 【论文复现】基于区块链的分布式光伏就地消纳交易模式研究(Matlab代码实现)
    ......
  • ecmascript 标准+ 严格模式与常规模式 + flat-flatMap 应用
    文章目录ecmascript历程严格模式与常规模式下的区别及注意事项严格模式下的属性删除Array.prototype.flat()和Array.prototype.flatMap()实例应用ecmascript历程变量声明要求常规模式:在常规模式下,使用var关键字声明变量时会出现变量提升现象。这意味着变......
  • 学英语学压测:07 jmeter 三种运行模式:GUI、命令行、Server
    ......
  • Sigrity System SI SerialLink模式进行USB3.0协议仿真分析操作指导-SuperSpeed_Rx_Dev
    SigritySystemSISerialLink模式进行USB3.0协议仿真分析操作指导-SuperSpeed_Rx_DeviceSigritySystemSISerialLink模式提供了10个协议合规性检查工具模板,用户可以将根据实际应用替换模板中的SPICE文件,然后进行协议仿真分析,同时软件还提供了目标结果的模板MASK以及该协议......
  • Sigrity System SI SerialLink模式进行USB3.0协议仿真分析操作指导-SuperSpeed_Tx_Hos
    SigritySystemSISerialLink模式进行USB3.0协议仿真分析操作指导-SuperSpeed_Tx_HostSigritySystemSISerialLink模式提供了10个协议合规性检查工具模板,用户可以将根据实际应用替换模板中的SPICE文件,然后进行协议仿真分析,同时软件还提供了目标结果的模板MASK以及该协议需......
  • 【Rust】枚举与模式匹配
    目录思维导图一、概述1.枚举的定义与使用2.特殊枚举:Option4.模式匹配5.iflet构造二、枚举1.枚举的定义与作用2.IP地址的枚举示例示例代码3.结构体与枚举的组合示例代码4.枚举变体的灵活性示例代码5.枚举的方法代码示例:6.Option枚举的优势标准库......
  • Java常用设计模式
    单例模式单例模式就是:在程序运行期间,某些类有且最多只有一个实例对象饿汉模式(静态常量)饥饿模式又称为饿汉模式,指的是JVM在加载类的时候就完成类对象的创建//饿汉式(静态常量)publicclassSingleton1{//构造器私有化,外部不能newprivateSingleton1(......
  • 【设计模式与体系结构】结构型模式-外观模式
    引言昨夜见军帖,可汗大点兵,军书十二卷,卷卷有爷名。阿爷无大儿,木兰无长兄,愿为市鞍马,从此替爷征。东市买骏马,西市买鞍鞯,南市买辔头,北市买长鞭。————《木兰诗节选》花木兰替父从军,欲买马、鞍鞯、辔头和长鞭,需要分别去往东市、西市、南市和北市。换言之,花木兰一人就得与四个贸易......
  • 观察者设计模式:原理、应用与对比分析
    引言在软件工程中,设计模式是解决常见问题的经典方案。观察者设计模式(ObserverPattern)是一种行为型设计模式,用于在对象间建立一种一对多的依赖关系,使得当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。本文旨在探讨观察者模式的基本原理、应用场景、具体案例,并与其......