重学 Java 设计模式:实战单例模式

简介:

重学 Java 设计模式:实战单例模式

一、前言
5个创建型模式的最后一个

在设计模式中按照不同的处理方式共包含三大类;创建型模式、结构型模式和行为模式,其中创建型模式目前已经介绍了其中的四个;工厂方法模式、抽象工厂模式、生成器模式和原型模式,除此之外还有最后一个单例模式。

掌握了的知识才是自己的

在本次编写的重学 Java 设计模式的编写中尽可能多的用各种场景案例还介绍设计的使用,包括我们已经使用过的场景;各种类型奖品发放、多套Redis缓存集群升级、装修公司报价清单和百份考卷题目与答案乱序,通过这些场景案例的实践感受设计模式的思想。但这些场景都是作者通过经验分离出来的,还并不是读者的知识,所以你如果希望可以融会贯通的掌握那么一定要亲力亲为的操作,事必躬亲的完成。

书不是看的是用的

在这里还是想强调一下学习方法,总有很多小伙伴对学习知识有疑惑,明明看了、看的时候也懂了,但到了实际使用的时候却用不上。或者有时候在想是不要是有更加生动的漫画或者什么对比会好些,当然这些方式可能会加快一个新人对知识的理解速度。但只要你把学习视频当电影看、学习书籍当故事看,就很难掌握这项技术栈。只有你把它用起来,逐字逐句的深挖,一点点的探求,把各项遇到的盲点全部扫清,才能让你真的掌握这项技能。

二、开发环境
JDK 1.8
Idea + Maven
涉及工程1个,可以通过关注公众号:bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)
三、单例模式介绍

单例模式可以说是整个设计中最简单的模式之一,而且这种方式即使在没有看设计模式相关资料也会常用在编码开发中。

因为在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。

综上以及我们平常的开发中,可以总结一条经验,单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。

四、案例场景
本章节的技术所出现的场景非常简单也是我们日常开发所能见到的,例如;

数据库的连接池不会反复创建
spring中一个单例模式bean的生成和使用
在我们平常的代码中需要设置全局的的一些属性保存
在我们的日常开发中大致上会出现如上这些场景中使用到单例模式,虽然单例模式并不复杂但是使用面却比较广。

五、7种单例模式实现
单例模式的实现方式比较多,主要在实现上是否支持懒汉模式、是否线程安全中运用各项技巧。当然也有一些场景不需要考虑懒加载也就是懒汉模式的情况,会直接使用static静态类或属性和方法的方式进行处理,供外部调用。

那么接下来我们就通过实现不同方式的实现进行讲解单例模式。

  1. 静态类使用
  2. class Singleton_00 {
public static Map<String,String> cache = new ConcurrentHashMap<String, String>();
AI 代码解读

}
以上这种方式在我们平常的业务开发中非常场常见,这样静态类的方式可以在第一次运行的时候直接初始化Map类,同时这里我们也不需要到延迟加载在使用。
在不需要维持任何状态下,仅仅用于全局访问,这个使用使用静态类的方式更加方便。
但如果需要被继承以及需要维持一些特定状态的情况下,就适合使用单例模式。

  1. 懒汉模式(线程不安全)
  2. class Singleton_01 {
private static Singleton_01 instance;

private Singleton_01() {
}

public static Singleton_01 getInstance(){
    if (null != instance) return instance;
    return new Singleton_01();
}
AI 代码解读

}
单例模式有一个特点就是不允许外部直接创建,也就是new Singleton_01(),因此这里在默认的构造函数上添加了私有属性 private。
目前此种方式的单例确实满足了懒加载,但是如果有多个访问者同时去获取对象实例你可以想象成一堆人在抢厕所,就会造成多个同样的实例并存,从而没有达到单例的要求。

  1. 懒汉模式(线程安全)
  2. class Singleton_02 {
private static Singleton_02 instance;

private Singleton_02() {
}

public static synchronized Singleton_02 getInstance(){
    if (null != instance) return instance;
    return new Singleton_02();
}
AI 代码解读

}
此种模式虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。如果不是特殊情况下,不建议此种方式实现单例模式。

  1. 饿汉模式(线程安全)
  2. class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();

private Singleton_03() {
}

public static Singleton_03 getInstance() {
    return instance;
}
AI 代码解读

}
此种方式与我们开头的第一个实例化Map基本一致,在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可。
但此种方式并不是懒加载,也就是说无论你程序中是否用到这样的类都会在程序启动之初进行创建。
那么这种方式导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将这些地图全部实例化。到你手机上最明显体验就一开游戏内存满了,手机卡了,需要换了。

  1. 使用类的内部类(线程安全)
  2. class Singleton_04 {
private static class SingletonHolder {
    private static Singleton_04 instance = new Singleton_04();
}

private Singleton_04() {
}

public static Singleton_04 getInstance() {
    return SingletonHolder.instance;
}
AI 代码解读

}
使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。
这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。
此种方式也是非常推荐使用的一种单例模式

  1. 双重锁校验(线程安全)
  2. class Singleton_05 {
private volatile static Singleton_05 instance;

private Singleton_05() {
}

public static Singleton_05 getInstance(){
   if(null != instance) return instance;
   synchronized (Singleton_05.class){
       if (null == instance){
           instance = new Singleton_05();
       }
   }
   return instance;
}
AI 代码解读

}
双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。
同时这种方式也满足了懒加载。
volatile关键字会强制的保证线程的可见性,而不加这个关键字,JVM也会尽力去保证可见性,但如果CPU一直处于繁忙状态就不确定了。

  1. CAS「AtomicReference」(线程安全)
  2. class Singleton_06 {
private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();

private static Singleton_06 instance;

private Singleton_06() {
}

public static final Singleton_06 getInstance() {
    for (; ; ) {
        Singleton_06 instance = INSTANCE.get();
        if (null != instance) return instance;
        INSTANCE.compareAndSet(null, new Singleton_06());
        return INSTANCE.get();
    }
}

public static void main(String[] args) {
    System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
    System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
}
AI 代码解读

}
java并发库提供了很多原子类来支持并发访问的数据安全性;AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。
AtomicReference 可以封装引用一个V实例,支持并发访问如上的单例方式就是使用了这样的一个特点。
使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。

  1. Effective Java作者推荐的枚举单例(线程安全)
  2. enum Singleton_07 {
INSTANCE;
public void test(){
    System.out.println("hi~");
}
AI 代码解读

}
约书亚·布洛克(英语:Joshua J. Bloch,1961年8月28日-),美国著名程序员。他为Java平台设计并实作了许多的功能,曾担任Google的首席Java架构师(Chief Java Architect)。

Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
这种方式解决了最主要的;线程安全、自由串行化、单一实例。
调用方式

@Test
public void test() {

Singleton_07.INSTANCE.test();
AI 代码解读

这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了串行化机制,绝对防止对此实例化,即使是在面对复杂的串行化或者反射攻击的时候。虽然这中方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

但也要知道此种方式在存在继承场景下是不可用的。

六、总结
虽然只是一个很平常的单例模式,但在各种的实现上真的可以看到java的基本功的体现,这里包括了;懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串行化等等。
在平时的开发中如果可以确保此类是全局可用不需要做懒加载,那么直接创建并给外部调用即可。但如果是很多的类,有些需要在用户触发一定的条件后(游戏关卡)才显示,那么一定要用懒加载。线程的安全上可以按需选择。
建议在学习的过程中一定要加以实践,否则很难完完整整的掌握一整套的知识体系。例如案例中的出现的Effective Java一书也非常建议大家阅读。另外推荐下这位大神的Github:https://github.com/jbloch
七、推荐阅读
重学 Java 设计模式:实战原型模式-模拟考试试卷乱序题目和答案
Java开发架构篇:初识领域驱动设计DDD落地
Java开发架构篇:DDD模型领域层决策规则树服务设计
Java开发架构篇:领域驱动设计架构基于SpringCloud搭建微服务
源码分析(面试常问题目) | Mybatis接口没有实现类为什么可以执行增删改查
讲道理,只要你是一个爱折腾的程序员,毕业找工作真的不需要再花钱培训!
作者:小傅哥

原文地址https://www.cnblogs.com/xiaofuge/p/13023749.html

目录
打赏
0
0
0
0
14
分享
相关文章
设计模式2:单例模式
单例模式是一种创建型模式,确保一个类只有一个实例,并提供全局访问点。分为懒汉式和饿汉式: - **懒汉式**:延迟加载,首次调用时创建实例,线程安全通过双重检查锁(double check locking)实现,使用`volatile`防止指令重排序。 - **饿汉式**:类加载时即创建实例,线程安全但可能浪费内存。 示例代码展示了如何使用Java实现这两种模式。
14 4
「全网最细 + 实战源码案例」设计模式——享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在减少大量相似对象的内存消耗。通过分离对象的内部状态(可共享、不变)和外部状态(依赖环境、变化),它有效减少了内存使用。适用于存在大量相似对象且需节省内存的场景。模式优点包括节省内存和提高性能,但会增加系统复杂性。实现时需将对象成员变量拆分为内在和外在状态,并通过工厂类管理享元对象。
166 92
「全网最细 + 实战源码案例」设计模式——命令模式
命令模式(Command Pattern)是一种行为型设计模式,将请求封装成独立对象,从而解耦请求方与接收方。其核心结构包括:Command(命令接口)、ConcreteCommand(具体命令)、Receiver(接收者)和Invoker(调用者)。通过这种方式,命令的执行、撤销、排队等操作更易扩展和灵活。 适用场景: 1. 参数化对象以操作。 2. 操作放入队列或远程执行。 3. 实现回滚功能。 4. 解耦调用者与接收者。 优点: - 遵循单一职责和开闭原则。 - 支持命令组合和延迟执行。 - 可实现撤销、恢复功能。 缺点: - 增加复杂性和类数量。
60 14
「全网最细 + 实战源码案例」设计模式——命令模式
「全网最细 + 实战源码案例」设计模式——责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,允许将请求沿着处理者链进行发送。每个处理者可以处理请求或将其传递给下一个处理者,从而实现解耦和灵活性。其结构包括抽象处理者(Handler)、具体处理者(ConcreteHandler)和客户端(Client)。适用于不同方式处理不同种类请求、按顺序执行多个处理者、以及运行时改变处理者及其顺序的场景。典型应用包括日志处理、Java Web过滤器、权限认证等。
56 13
「全网最细 + 实战源码案例」设计模式——责任链模式
「全网最细 + 实战源码案例」设计模式——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,用于定义一系列可替换的算法或行为,并将它们封装成独立的类。通过上下文持有策略对象,在运行时动态切换算法,提高代码的可维护性和扩展性。适用于需要动态切换算法、避免条件语句、经常扩展算法或保持算法独立性的场景。优点包括符合开闭原则、运行时切换算法、解耦上下文与策略实现、减少条件判断;缺点是增加类数量和策略切换成本。示例中通过定义抽象策略接口和具体策略类,结合上下文类实现动态算法选择。
57 8
「全网最细 + 实战源码案例」设计模式——策略模式
「全网最细 + 实战源码案例」设计模式——模板方法模式
模板方法模式是一种行为型设计模式,定义了算法的骨架并在父类中实现不变部分,将可变部分延迟到子类实现。通过这种方式,它避免了代码重复,提高了复用性和扩展性。具体步骤由抽象类定义,子类实现特定逻辑。适用于框架设计、工作流和相似算法结构的场景。优点包括代码复用和符合开闭原则,缺点是可能违反里氏替换原则且灵活性较低。
64 7
「全网最细 + 实战源码案例」设计模式——模板方法模式
|
1月前
|
「全网最细 + 实战源码案例」设计模式——模式扩展(配置工厂)
该设计通过配置文件和反射机制动态选择具体工厂,减少硬编码依赖,提升系统灵活性和扩展性。配置文件解耦、反射创建对象,新增产品族无需修改客户端代码。示例中,`CoffeeFactory`类加载配置文件并使用反射生成咖啡对象,客户端调用时只需指定名称即可获取对应产品实例。
89 40
「全网最细 + 实战源码案例」设计模式——适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,通过引入适配器类将一个类的接口转换为客户端期望的另一个接口,使原本因接口不兼容而无法协作的类能够协同工作。适配器模式分为类适配器和对象适配器两种,前者通过多重继承实现,后者通过组合方式实现,更常用。该模式适用于遗留系统改造、接口转换和第三方库集成等场景,能提高代码复用性和灵活性,但也可能增加代码复杂性和性能开销。
72 28
「全网最细 + 实战源码案例」设计模式——组合模式
组合模式(Composite Pattern)是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构。它允许客户端以一致的方式对待单个对象和对象集合,简化了复杂结构的处理。组合模式包含三个主要组件:抽象组件(Component)、叶子节点(Leaf)和组合节点(Composite)。通过这种模式,客户端可以统一处理简单元素和复杂元素,而无需关心其内部结构。适用于需要实现树状对象结构或希望以相同方式处理简单和复杂元素的场景。优点包括支持树形结构、透明性和遵循开闭原则;缺点是可能引入不必要的复杂性和过度抽象。
80 22
「全网最细 + 实战源码案例」设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过代理对象控制对目标对象的访问并添加额外功能。它分为静态代理和动态代理,后者包括JDK动态代理和CGLIB动态代理。JDK动态代理基于接口反射生成代理类,而CGLIB通过继承目标类生成子类。代理模式适用于延迟初始化、访问控制、远程服务、日志记录和缓存等场景,优点是职责分离、符合开闭原则和提高安全性,缺点是增加系统复杂性。
77 25

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等