[设计模式Java实现附plantuml源码~创建型] 确保对象的唯一性~单例模式

简介: [设计模式Java实现附plantuml源码~创建型] 确保对象的唯一性~单例模式

前言:

为什么之前写过Golang 版的设计模式,还在重新写Java 版?

答:因为对于我而言,当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言,更适合用于学习设计模式。

为什么类图要附上uml

因为很多人学习有做笔记的习惯,如果单纯的只是放一张图片,那么学习者也只能复制一张图片,可复用性较低,附上uml,方便有新理解时,快速出新图。


饿汉单例

@startuml
class Singleton {
  - static instance = new Singleton
  + static getInstance(): Singleton
  + operation(): void
}
Singleton --> Singleton : getInstance()
@enduml

懒汉单例

@startuml
class Singleton {
  - static instance = null
  - constructor: Singleton
  + static getInstance(): Singleton
  + operation(): void
}
Singleton --> Singleton : getInstance()
@enduml

懒汉加锁

@startuml
class Singleton {
  - static volatile instance = null
  - constructor: Singleton
  + static getInstance(): synchronized Singleton
  + operation(): void
}
note left of Singleton::getInstance
if (instance == null)
  instance = construct();
return instance;
end note
Singleton --> Singleton : getInstance()
@enduml

懒汉双重检测

@startuml
class Singleton {
  - static volatile instance = null
  - constructor: Singleton
  + static getInstance(): synchronized Singleton
  + operation(): void
}
note left of Singleton::getInstance
// 一重检测
if (instance == null){
    synchronized(..) {
//  二重检测
        if(instance == null) {
              instance = construct();
        }
    }
}
return instance;
end note
Singleton --> Singleton : getInstance()
@enduml

代码实现

饿汉
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有构造方法,防止外部实例化
    private EagerSingleton() {
    }

    // 获取单例对象的静态方法
    public static EagerSingleton getInstance() {
        return instance;
    }
}


EagerSingleton 类的构造方法被声明为私有,以防止外部通过 new 关键字实例化该类。而类中定义了一个私有静态变量 instance,它在类加载的时候就被创建并初始化为 EagerSingleton 类的一个实例。

getInstance() 方法是获取单例对象的静态方法,它直接返回了已经创建好的 instance 对象。

由于饿汉单例在类加载的时候就创建了对象,因此它具有线程安全的特性,但可能会造成一定的资源浪费,因为无论是否使用该单例对象,都会被提前创建。


懒汉单锁
public class LazySingleton {
    private static LazySingleton instance;

    // 私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    // 获取单例对象的静态方法,使用synchronized关键字实现线程安全
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
懒汉双重检测
public class LazySingleton {
    private static volatile LazySingleton instance;

    // 私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    // 获取单例对象的静态方法,使用双重检测实现延迟加载和线程安全
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

在上面的代码中,LazySingleton 类的构造方法被声明为私有,以防止外部通过 new 关键字实例化该类。类中定义了一个私有静态变量 instance,它在第一次调用 getInstance() 方法时才会被创建并初始化为 LazySingleton 类的一个实例。

getInstance() 方法是获取单例对象的静态方法。在方法中,首先检查 instance 是否为空,如果为空则进入同步代码块。在同步代码块内部,再次检查 instance 是否为空,这是为了防止多个线程同时通过了第一个检查而进入同步块,

从而创建多个实例。如果 instance 仍然为空,则创建新的实例并将其赋值给 instance

使用 volatile 关键字修饰 instance 变量可以保证变量的可见性,从而避免在多线程环境下出现问题。

懒汉双重检测通过双重检查和同步块的方式实现了延迟加载和线程安全,同时也提高了性能。因此,它是一种常见的单例模式实现方式。

饿汉式单例类不能实现延迟加载,不管将来用不用,它始终占据内存;

懒汉式单例类线程安全控制烦琐,而且性能受影响。

可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题。

有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是肯定的。下面来学习这种更好的被称为 Initialization on Demand Holder(IoDH) 的技术。实现 IoDH 时,需在单例类中增加一个静态 (static) 内部类,在该内部类中创建单例对象,再将该单例对象通过 getInstance() 方法返回给外部使用

IODH实现懒汉模式
public class LazySingleton {
    private static class SingletonHolder {
        private static final LazySingleton instance = new LazySingleton();
    }

    // 私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    // 获取单例对象的静态方法
    public static LazySingleton getInstance() {
        return SingletonHolder.instance;
    }
}

在上面的代码中,LazySingleton 类内部定义了一个私有静态内部类 SingletonHolder。在 SingletonHolder 内部,定义了一个私有静态变量 instance它是 LazySingleton 类的一个实例。

由于静态内部类 SingletonHolder 只有在 getInstance() 方法被调用时才会被加载,所以在类加载的过程中并不会创建 instance 对象。只有当第一次调用getInstance() 方法时,SingletonHolder 类会被加载,从而创建并初始化 instance 对象。这种方式利用了 Java 类加载的线程安全性和延迟加载的特性,实现了懒汉模式。

因此,通过 IODH 实现懒汉模式,可以在需要时延迟创建单例对象,并且保证了线程安全性。

单例模式总结

单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。

1.主要优点单例模式的主要优点如下:

(1)单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

(2)由于在系统内存中只存在一个对象,因此可以节约系统资源。对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。(3)允许可变数目的实例。基于单例模式,开发人员可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了由于单例对象共享过多有损性能的问题。(注:自行提供指定数目实例对象的类可称之为多例类。)

单例模式的主要缺点如下:

(1)由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

(2)单例类的职责过重,在一定程度上违背了单一职责原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。

(3)现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

3.适用场景在以下情况下可以考虑使用单例模式:

(1)系统只需要一个实例对象。例如,系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。

(2)客户调用类的单个实例只允许使用一个公共访问点。除了该公共访问点,不能通过其他途径访问该实例。

相关文章
|
4天前
|
设计模式 算法 Java
Java一分钟之-设计模式:策略模式与模板方法
【5月更文挑战第17天】本文介绍了策略模式和模板方法模式,两种行为设计模式用于处理算法变化和代码复用。策略模式封装不同算法,允许客户独立于具体策略进行选择,但需注意选择复杂度和过度设计。模板方法模式定义算法骨架,延迟部分步骤给子类实现,但过度抽象或滥用继承可能导致问题。代码示例展示了两种模式的应用。根据场景选择合适模式,以保持代码清晰和可维护。
10 1
|
4天前
|
设计模式 Java
Java一分钟之-设计模式:装饰器模式与代理模式
【5月更文挑战第17天】本文探讨了装饰器模式和代理模式,两者都是在不改变原有对象基础上添加新功能。装饰器模式用于动态扩展对象功能,但过度使用可能导致类数量过多;代理模式用于控制对象访问,可能引入额外性能开销。文中通过 Java 代码示例展示了两种模式的实现。理解并恰当运用这些模式能提升代码的可扩展性和可维护性。
19 1
|
4天前
|
设计模式 Java
Java一分钟之-设计模式:工厂模式与抽象工厂模式
【5月更文挑战第17天】本文探讨了软件工程中的两种创建型设计模式——工厂模式和抽象工厂模式。工厂模式提供了一个创建对象的接口,延迟实例化到子类决定。过度使用或违反单一职责原则可能导致问题。代码示例展示了如何创建形状的工厂。抽象工厂模式则用于创建一系列相关对象,而不指定具体类,但添加新产品可能需修改现有工厂。代码示例展示了创建颜色和形状的工厂。根据需求选择模式,注意灵活性和耦合度。理解并恰当运用这些模式能提升代码质量。
16 2
|
5天前
|
设计模式 搜索推荐 数据库连接
第二篇 创建型设计模式 - 灵活、解耦的创建机制
第二篇 创建型设计模式 - 灵活、解耦的创建机制
|
1天前
|
监控 NoSQL Java
java云MES 系统源码Java+ springboot+ mysql 一款基于云计算技术的企业级生产管理系统
MES系统是生产企业对制造执行系统实施的重点在智能制造执行管理领域,而MES系统特点中的可伸缩、信息精确、开放、承接、安全等也传递出:MES在此管理领域中无可替代的“王者之尊”。MES制造执行系统特点集可伸缩性、精确性、开放性、承接性、经济性与安全性于一体,帮助企业解决生产中遇到的实际问题,降低运营成本,快速适应企业不断的制造执行管理需求,使得企业已有基础设施与一切可用资源实现高度集成,提升企业投资的有效性。
28 5
|
3天前
|
监控 安全 NoSQL
采用java+springboot+vue.js+uniapp开发的一整套云MES系统源码 MES制造管理系统源码
MES系统是一套具备实时管理能力,建立一个全面的、集成的、稳定的制造物流质量控制体系;对生产线、工艺、人员、品质、效率等多方位的监控、分析、改进,满足精细化、透明化、自动化、实时化、数据化、一体化管理,实现企业柔性化制造管理。
25 3
|
4天前
|
存储 Java
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
|
4天前
|
数据采集 监控 安全
java数字工厂MES系统全套源码Java+idea+springboot专业为企业提供智能制造MES解决方案
"MES" 指的是制造执行系统(Manufacturing Execution System)。MES在制造业中扮演着至关重要的角色,它是位于企业资源计划(ERP)系统和车间控制系统之间的系统,用于实时收集、管理、分析和报告与制造过程相关的数据。
13 0
|
4天前
|
移动开发 监控 供应链
JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(app、小程序、H5、台后管理端)
一开始接触MES系统,很多人会和博主一样,对MES细节的应用不了解,这样很正常,因为MES系统相对于其他系统来讲应用比较多!
16 1
JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(app、小程序、H5、台后管理端)
|
4天前
|
设计模式 Java
Java一分钟之-设计模式:观察者模式与事件驱动
【5月更文挑战第17天】本文探讨了Java中实现组件间通信的观察者模式和事件驱动编程。观察者模式提供订阅机制,当对象状态改变时通知所有依赖对象。然而,它可能引发性能问题、循环依赖和内存泄漏。代码示例展示了如何实现和避免这些问题。事件驱动编程则响应用户输入和系统事件,但回调地狱和同步/异步混淆可能造成困扰。JavaFX事件驱动示例解释了如何处理事件。理解这两种模式有助于编写健壮的程序。
10 1