首页 > 其他分享 >使用双重检查锁定技术保证多线程中单例模式的线程安全

使用双重检查锁定技术保证多线程中单例模式的线程安全

时间:2023-06-11 16:13:59浏览次数:60  
标签:singleton null 互斥 实例 线程 单例 多线程

使用双重检查锁定技术保证多线程中单例模式的线程安全

前言

单例模式是一种设计模式,保证一个类只有一个实例,并且在整个应用中共享。它适用于需要控制对共享资源的访问,例如数据库连接、配置文件或日志记录器。

但是,在多线程环境下实现单例模式可能比较棘手。如果多个线程同时尝试创建单例实例,我们可能会得到多个实例,这违反了单例原则。因此,我们需要保证单例创建是线程安全的,也就是说只有一个线程能够创建实例,其他线程则需要等待实例可用再获取实例。

一种常见的在多线程中实现单例模式线程安全的方法是使用互斥机制,例如锁,来保护创建单例实例的关键代码段。然而,使用互斥会带来性能开销和潜在的死锁风险。而且,不需要对每次访问单例实例的过程都进行互斥,因为大多数情况下实例已经创建好了,只需要返回它。

这时候,双重检查锁定技术就派上用场了。这是一种实现线程安全单例模式的方法。

双重检查锁定技术

双重检查锁定的思想是在创建单例实例之前,先检查两次是否为 null。第一次检查不使用任何同步手段,第二次检查则在一个互斥块中进行。代码结构如下:

if (singleton == null) // 第一次检查
{
    synchronize (lock) // 获取锁
    {
        if (singleton == null) // 第二次检查
        {
            singleton = new Singleton(); // 创建实例
        }
    }
    release (lock) // 释放锁
}
return singleton; // 返回实例

这种技术的逻辑如下:

  • 如果单例实例不为 null,我们可以直接返回它,无需进行互斥。这避免了不必要的锁,也提高了性能。
  • 如果单例实例为 null,我们需要获取一个锁进入一个互斥块。这保证了只有一个线程能够一次创建实例。
  • 在互斥块中,我们需要再次检查单例实例是否为 null。这是因为另一个线程可能在我们获取锁之前已经创建了实例。如果仍然为 null,我们可以安全地创建一个新的实例。否则,我们可以直接返回已存在的实例。
  • 在创建或返回实例之后,我们需要释放锁并退出互斥块。这允许其他线程访问单例实例。

通过使用双重检查锁定,我们可以实现线程安全、最少互斥及做到高性能。但是,在不同的编程语言中实现这种技术时,我们需要注意一些注意事项和挑战。

在 C# 中使用双重检查锁定技术

在 C# 中,实现双重检查锁定需要将单例字段声明为 volatile ,并且使用私有构造函数。

volatile 关键字保证了单例字段在所有线程中可见和一致。没有 volatile 的话,有可能一个线程看到的是一个部分构造的单例类的实例,因为编译器重排序或内存缓存的原因。这可能导致意外的错误或程序崩溃。

一般来说,volatile 适用于变量的读写操作不依赖于其他变量的情况,而 synchronized 适用于多个线程对同一个对象进行读写操作的情况。

因为 volatile 关键字只能保证单个变量的原子性,不能保证复合操作的原子性。例如,对于 i++ 这样的自增操作,如果 i 是 volatile 的,那么只能保证读取 i 和写入 i 的原子性,不能保证整个自增操作的原子性。要实现复合操作的原子性,需要使用同步机制或原子类。

私有构造函数阻止了其他类创建新的单例类的实例。没有私有构造函数的话,其他类可能通过反射或序列化绕过单例逻辑并创建多个实例。

在 C# 中双重检查锁定的完整代码如下:

public class Singleton {

    // 将单例字段声明为 volatile
    private static volatile Singleton singleton;

    // 使用私有构造函数
    private Singleton() {}

    // 使用公共静态方法获取或创建单例实例
    public static Singleton GetInstance() {
        if (singleton == null) { // 第一次检查
            lock (typeof(Singleton)) { // 获取锁
                if (singleton == null) { // 第二次检查
                    singleton = new Singleton(); // 创建实例
                }
            }
        }
        return singleton; // 返回实例
    }
}

标签:singleton,null,互斥,实例,线程,单例,多线程
From: https://www.cnblogs.com/netlog/p/17473062.html

相关文章

  • 线程同步与异步套接字编程
    事件对象时间对象也属于内核对象,包含一个使用计数,一个用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是为通知状态的布尔值有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件,当人工重置的事件得到......
  • 5.28学习总结thread多线程理解
    多线程早在大二刚来的时候就听王建民老师提到过,但是当时觉得多线程肯定很难,而且现在也用不到,就没有接触。现在看来多线程的学习还是比较简单的。下面演示代码均为PythonfromthreadingimportThreadth=thread(target=,args=())#target指向新线程执行的目标函数,args中......
  • 面试官:在项目中,你是如何使用线程池的?
    大家好,我是田哥前两天,有位星友(知识星球里的朋友简称)私信我,问在项目中如何使用线程池,关于线程池的原理和八股文相关的都可以背,但是要是问到你们项目中是怎么用的,心里总是有点慌。公众号里回复77,获取面试小抄和面试相关资源:话不多说,我们直接步入正题。创建线程池的方式我在这篇文章......
  • Java基础语法(二十):创建线程
    前言在计算机科学中,多线程是指在单个程序中同时执行多个线程。Java是一种支持多线程编程的语言,Java中的线程可以通过继承Thread类或实现Runnable接口来创建。本文将介绍Java多线程的基本概念和如何创建线程。介绍在Java中,线程是一种轻量级的进程,它可以与其他线程共享同一个进程的内......
  • (2023.6.10)线程绑定到指定核上
    pthread_setaffinity_np与sched_setaffinity的区别:sched_setaffinity可在进程的线程中去修改亲和性写在启动脚本中是使用pthread_setaffinity_np、sched_setaffinity、还是tasklet?(https://www.cnblogs.com/x_wukong/p/5924298.html)c语言如何调用到系统命令reboot? 同时在......
  • java设计模式之(单例模式)
    单例模式(懒汉式和饿汉式)    在Java中指的是单例设计模式他是软件开发中最常用的设计模式之一。单:唯一例:实例单例设计模式:既某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式要点:  1.某个类只能有一个实例       构造器私有  2.它必须......
  • QT多线程(线程互斥)
    (文章目录)前言线程互斥是指在多线程并发执行时,为避免多个线程访问共享资源时发生冲突而采取的一种机制。本篇文章我们就这个问题来了解一下什么叫线程互斥,又如何解决线程互斥的问题。一、导致问题产生的原因和解决方法如果多个线程同时访问同一共享资源,可能会导致数据不一致......
  • QT多线程基础
    (文章目录)前言本篇文章来讲解一下QT中的多线程使用方法。其实线程这个概念对于我们来说并不陌生,main函数在多线程中一般就被称为主线程。在QT中,使用QThread类可以方便地创建新的线程并在其中执行任务。以下介绍一些常用的QT多线程的技术和方法。一、多线程概念介绍多线程是......
  • Python多线程编程的一个掉进去不太容易爬出来的坑
    原文复制过来很多图片不能显示,发个链接吧。是使用Python+Socket编程模拟FTP工作原理的代码,多线程会引入一个坑,使用多进程不存在这个问题。原文地址 ......
  • 详解Python线程对象daemon属性对线程退出的影响
    进程、线程的概念以及多线程编程的基础知识请参考文末给出的方式在公众号历史文章中查找相关文章进行阅读。本文重点介绍线程对象daemon属性在线程退出时产生的作用和影响。首先,我们来看一下官方文档对守护线程(daemonthread)的描述:再来看一下官方文档对线程对象daemon属性的描述:可......