[设计模式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)客户调用类的单个实例只允许使用一个公共访问点。除了该公共访问点,不能通过其他途径访问该实例。

相关文章
|
19天前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
198 35
|
19天前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
173 8
|
20天前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
191 0
|
20天前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
155 2
设计模式 存储 人工智能
163 0
|
3月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
3月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
3月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
3月前
|
缓存 安全 Java
Java反射机制:动态操作类与对象
Java反射机制是运行时动态操作类与对象的强大工具,支持获取类信息、动态创建实例、调用方法、访问字段等。它在框架开发、依赖注入、动态代理等方面有广泛应用,但也存在性能开销和安全风险。本文详解反射核心API、实战案例及性能优化策略,助你掌握Java动态编程精髓。
|
Java
Java并发编程笔记之FutureTask源码分析
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
4385 0