【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

简介: 设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。

1. 前言

设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。


设计模式有很多,本文主要介绍单例模式.

08c287700ab546cab2b269f6a08961fe.gif


2. 单例模式

单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来获取该实例。


3. 如何保证一个类只有一个实例

在Java中,通常使用static关键字来保证 类只有唯一的实例.


在单例模式中,类的构造函数被私有化,以防止外部代码直接创建实例。然后,通过一个静态方法或静态变量来获取类的唯一实例。


如果实例不存在,则创建一个新的实例并返回;如果实例已存在,则直接返回该实例。


而是实现上述的方法有很多,下面介绍三种常见的实现模式:


1.饿汉式

2.懒汉式

3.静态内部类



4. 饿汉式单例模式

饿汉式单例: 在类加载时就创建实例,保证在任何情况下都有一个实例可用。


代码示例:

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {
    // 私有化构造函数
    }
    public static Singleton getInstance() {
        return instance;
    }
}

代码分析:


df8189cff6b048b0a8837859589fd47c.png



私有化构造函数使得外部无法直接创建实例,如果其它线程想到得到Singleton类的实例,只能通过Singleton类提供的getInstance()方法得到.


5. 懒汉式单例模式

懒汉式单例: 延迟加载实例,只有在需要时才创建实例。


代码示例:


public class SingletonLazy {
    private static SingletonLazy instance = null;
    private SingletonLazy(){
    // 私有化构造方法
    }
    public static SingletonLazy getInstance(){
        if (instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
}

代码分析:


82a4eca4944c46e6a3a8c7a1b5fc16e7.png



如果后续代码中没有调用getInstance方法,那么就把创建实例那一步给省下来了.


上述代码中,那种实现单例模式的方式是线程安全的?


答案是 饿汉模式


饿汉模式并没有涉及到修改,而懒汉模式即涉及到读有涉及到修改.那么在多线程模式中就不安全了.


设想一个场景, 如果在懒汉模式下,两个线程同时去调用getInstance方法,如果一个线程读到的instance为null,并给instance进行实例的创建,而另外一个线程读到的instance还是为null,又给instance创建了一次实例.那么instance就被创建多次了


6. 实现线程安全的懒汉式单例

既然懒汉模式在多线程的环境下不安全,那么如果保证懒汉模式在多线程的环境下安全呢?


既然涉及到多线程,就离不开"锁",也就是 synchronized


示例:


bd7a32c556e945fa8b328247d311e9d5.png


通过加锁的方式,解决了线程安全问题,但是又带来了新的问题.


懒汉式单例只是第一次调用getInstance方法时才会发生线程不安全问题.一旦创建好了,线程就安全了.


但上述通过加锁的方式,会导致线程已经安全时仍然时需要加锁,有些多此一举了. 且加锁开销比较大,影响效率.


那么如果保证线程安全时不加锁呢?


实例没有创建前,线程不安全.实例创建之后,线程安全. 那么就可以在加锁操作前在进行一次判断.

public class SingletonLazy {
    private static SingletonLazy instance = null;
    private SingletonLazy(){
    }
    public static SingletonLazy getInstance(){
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

代码分析:



7a0a29b05a804f7bb2d1cd75e94c0d4d.png


使用双重if保证只创建一次实例以及保证在线程安全时不进行加锁. 这里要重点进行理解.


但仔细思考上述代码仍然有一个问题.


假如两个线程同时调用getInstance方法,第一个线程拿到锁,进入第二层if,开始进行new对象.


这里的new操作可以分为:


1.申请内存,得到内存首地址

2.调用构造方法,来初始化实例

3.把内存的首地址赋值给instance引用

上述的new操作,编译器可能会进行"指令重排序". 就可能会导致无序写入的问题


上述步骤中的2和3,在单线程环境下是可以调换顺序的,并不会影响结果.


但在多线程环境中就会导致无序写入问题:


1.线程A执行到instance = new Singleton();这行代码时,由于指令重排序,可能会先执行分配内存空间、初始化实例对象、将instance指向内存空间这三个操作的任意组合,而不是按照代码顺序执行。

2.假设线程A执行完了分配内存空间和初始化实例对象的操作,但还没有将instance指向内存空间,此时线程B执行到第一个instance == null的判断,结果为false,就会直接返回instance,但此时instance并没有正确初始化。

为了解决上述问题,就可以使用volatile关键字. 它可以禁止指令重排序优化,保证instance的写操作先于读操作,从而避免其他线程在没有正确初始化实例时获取到instance。


public class SingletonLazy {
    private static volatile SingletonLazy instance = null;
    private SingletonLazy(){
    }
    public static SingletonLazy getInstance(){
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

上述就是一个在多线程环境下完全安全的懒汉式单例模式的写法.


7. 静态内部类实现单例模式

除了上述方式,使用静态内部类也能实现单例模式.


该方式利用静态内部类的特性来实现懒加载和线程安全。


代码示例:

public class Singleton {
    private Singleton() {
        // 私有化构造函数
    }
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

静态内部类SingletonHolder持有Singleton的唯一实例,当第一次调用getInstance方法时,才会触发SingletonHolder的初始化,从而创建实例。


由于静态内部类的初始化是线程安全的,因此可以确保在多线程环境下只有一个实例被创建。


8. 总结

本文主要介绍了饿汉式,懒汉式和静态内部类三种实现单例模式的方式,其中 懒汉式单例 很重要,要着重理解双重if的含义.


需要注意的是,以上方式都可以在多线程环境下保证单例的正确创建,但在特殊情况下,如使用反射或序列化/反序列化等机制,仍然可能破坏单例的唯一性。


在实际应用中,需要根据具体场景和需求选择合适的单例模式实现方式。


5cf20061bb5f4d108508ee06f60b0eaf.gif

相关文章
|
2月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
134 0
|
4月前
|
设计模式 缓存 安全
【设计模式】【创建型模式】单例模式(Singleton)
一、入门 什么是单例模式? 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。它常用于需要全局唯一对象的场景,如配置管理、连接池等。 为什么要单例模式? 节省资源 场景:某些对象创
162 15
|
3月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
231 5
|
3月前
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
243 3
|
4月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
|
4月前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
124 1
|
5月前
|
设计模式 存储 安全
设计模式-单例模式练习
单例模式是Java设计模式中的重要概念,确保一个类只有一个实例并提供全局访问点。本文详解单例模式的核心思想、实现方式及线程安全问题,包括基础实现(双重检查锁)、懒汉式与饿汉式对比,以及枚举实现的优势。通过代码示例和类图,深入探讨不同场景下的单例应用,如线程安全、防止反射攻击和序列化破坏等,展示枚举实现的简洁与可靠性。
106 0
|
5月前
|
数据采集 存储 网络协议
Java HttpClient 多线程爬虫优化方案
Java HttpClient 多线程爬虫优化方案
|
4月前
|
设计模式 Java 数据库连接
【设计模式】【创建型模式】工厂方法模式(Factory Methods)
一、入门 什么是工厂方法模式? 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类决定实例化哪个类。工厂方法模式使类的实例化延迟
128 16
|
4月前
|
设计模式 负载均衡 监控
并发设计模式实战系列(2):领导者/追随者模式
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发设计模式实战系列,第二章领导者/追随者(Leader/Followers)模式,废话不多说直接开始~
125 0

热门文章

最新文章