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

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

目录


  • 一、前言
  • 二、开发环境
  • 三、单例模式介绍
  • 四、案例场景
  • 五、7种单例模式实现
  • 0. 静态类使用
  • 1. 懒汉模式(线程不安全)
  • 2. 懒汉模式(线程安全)
  • 3. 饿汉模式(线程安全)
  • 4. 使用类的内部类(线程安全)
  • 5. 双重锁校验(线程安全)
  • 6. CAS「AtomicReference」(线程安全)
  • 7. Effective  Java作者推荐的枚举单例(线程安全)
  • 六、总结


一、前言

5个创建型模式的最后一个

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

掌握了的知识才是自己的

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

书不是看的是用的

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

二、开发环境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程1个,可以通过关注「公众号」bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)

三、单例模式介绍

0.jpg

单例模式,图片来自 refactoringguru.cn

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

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

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

四、案例场景

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

  1. 数据库的连接池不会反复创建
  2. spring中一个单例模式bean的生成和使用
  3. 在我们平常的代码中需要设置全局的的一些属性保存

在我们的日常开发中大致上会出现如上这些场景中使用到单例模式,虽然单例模式并不复杂但是使用面却比较广。

五、7种单例模式实现

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

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

0. 静态类使用

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

1. 懒汉模式(线程不安全)

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

2. 懒汉模式(线程安全)

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

3. 饿汉模式(线程安全)

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

4. 使用类的内部类(线程安全)

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

5. 双重锁校验(线程安全)

public 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;
    }
}
  • 双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。
  • 同时这种方式也满足了懒加载。

6. CAS「AtomicReference」(线程安全)

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

7. Effective  Java作者推荐的枚举单例(线程安全)

public enum Singleton_07 {
    INSTANCE;
    public void test(){
        System.out.println("hi~");
    }
}

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

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

「调用方式」

@Test
public void test() {
    Singleton_07.INSTANCE.test();

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

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

六、总结

  • 虽然只是一个很平常的单例模式,但在各种的实现上真的可以看到java的基本功的体现,这里包括了;懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串行化等等。
  • 在平时的开发中如果可以确保此类是全局可用不需要做懒加载,那么直接创建并给外部调用即可。但如果是很多的类,有些需要在用户触发一定的条件后(游戏关卡)才显示,那么一定要用懒加载。线程的安全上可以按需选择。
  • 建议在学习的过程中一定要加以实践,否则很难完完整整的掌握一整套的知识体系。例如案例中的出现的Effective Java一书也非常建议大家阅读。另外推荐下这位大神的Github:https://github.com/jbloch
目录
相关文章
|
11天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
16 2
|
25天前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
57 2
|
30天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
27 2
|
22天前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
92 65
|
11天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
1月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
11天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
19天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
14 1
|
22天前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
36 0
[Java]23种设计模式
|
28天前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
44 1

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    42
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    46
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    54
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    62
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    57
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    41
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    106
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78