• 单例模式及线程安全的实践


    🌟 欢迎来到 我的博客! 🌈

    💡 探索未知, 分享知识 !💫

    在这里插入图片描述


    引言

    单例模式是个挺实用的设计模式,它的要点就是确保一个类只有一个实例,并且提供一个访问这个实例的全局点。这种模式在你需要控制资源或者保持全局状态的时候特别有帮助。但在多线程的情况下,实现这个模式就需要一些技巧,以确保安全和效率。下面咱们就一起看看怎么实现一个既安全又高效的单例模式

    基本的单例模式长啥样?

    先来看一个最简单的单例模式示例:

    public class Singleton {
        private static Singleton instance;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    这个例子简单明了,但在多线程的场景下就可能出问题了。比如,如果两个线程同时检查到 instance为空,那么它们就都会创建一个实例,这就不符合单例模式的初衷了。

    PS :
    就像图示中,这样就创建了多个实例对象

    在这里插入图片描述

    饿汉式单例

    这段代码实现的是所谓的饿汉式单例模式。在这种模式下,单例的实例在类被加载到JVM时就立即初始化了。这种方式简单直接,因为它依靠JVM类加载机制保证实例的唯一性,同时也无需担心多线程问题,因为类加载过程是线程安全的。

    class Singleton {
        private static Singleton instance = new Singleton();
    
        public static Singleton getInstance() {
            return instance;
        }
    
        private Singleton() { }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    优点是实现简单,类加载时完成初始化,避免了线程同步问题。

    缺点是如果这个类比较大,而且在程序启动时就加载,但长时间不使用,会导致资源浪费。


    怎样才能线程安全?

    可以给获取实例的方法加个锁:
    public class Singleton {
        private static Singleton instance;
    
        private Singleton() {}
    
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这种方法虽然简单,但每次调用 getInstance() 都会加锁,可能会拖慢速度。

    懒汉模式 ( 双 重 检 查 )

    这个方法更聪明点,只在需要时加锁:

    public class Singleton {
        private static volatile Singleton instance;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这样,只有在第一次创建实例时才会同步,提高了效率。而且,用了 volatile 关键字来确保变量的可见性。

    这种方式的关键在于`instance`字段的`volatile`关键字和`synchronized`块。`volatile`确保当`instance`变量被初始化成单例实例时,多个线程正确地处理`instance`变量,`synchronized`块则确保在实例未初始化前只有一个线程能进入初始化代码区。

    优点 : 只有在实际使用时才会创建实例。并且通过双重检查锁定机制减少了锁的粒度,提高了效率。
    缺点 : 实现复杂,需要多重检查以确保线程安全。但我个人更推荐使用这种方法

    🎉总结🎉

    🎈在多线程环境下实现一个既安全又高效的单例模式需要一些技巧。根据你的需求和具体情况,你可以选择加锁、双重检查或者静态内部类的方法。如果你在乎性能,双重检查和静态内部类是不错的选择,因为它们在保证了线程安全的同时,也考虑到了效率。

  • 相关阅读:
    104. 二叉树的最大深度 | 层序遍历 | 遍历 | 子问题 | TypeScript
    【2024秋招】用友项目管理部门java后端二面2023.9.12
    NTP8928(20W内置DSP双通道D类功放芯片)
    Java(solon) -VS- Go(gin) 之内存与并发测试
    安卓期中汇总回顾
    Scratch软件编程等级考试一级——20220619
    java计算机毕业设计基于springboot+vue+elementUI的在线彩妆化妆品销售店铺商城(前后端分离)
    leetcode 1562. 查找大小为 M 的最新分组
    springboot-kafka
    [附源码]计算机毕业设计JAVAjsp校园志愿者服务管理系统
  • 原文地址:https://blog.csdn.net/m0_64583485/article/details/136563695