双重检查锁单例

简介: 双重检查锁单例

1、简介

双重检查锁单例:使用双重检查锁机制来实现懒汉式单例。它的基本思想是使用两次if 判断,保证在多线程环境下创建单例对象时不会出现竞态条件。

2、优缺点

双重检查锁机制的优点是,在单例对象已经初始化之后,就不需要再进行同步。这样可以提高系统性能。但是,双重检查锁机制的实现过程复杂,需要注意一些细节。例如,使用volatile 关键字修饰 INSTANCE 变量,以及在获取单例对象时使用双重 if 判断。

3、代码实现

下面是使用双重检查锁机制的 Java 代码示例:

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

4、为什么使用volatile

在代码示例中,使用了volatile 关键字修饰 INSTANCE 变量。这是因为,在 Java 语言中,当多个线程并发执行时,会发生指令重排序。

如果不使用 volatile 关键字,就可能会出现创建单例对象的过程被拆分成多个步骤,例如:

①为单例对象分配内存空间。

②初始化单例对象。

③将INSTANCE 变量指向分配的内存空间。

在没有volatile 关键字的情况下,步骤 ② 和 ③ 可能会被重排序。

这就可能导致其他线程在执行getInstance() 方法时,看到的 INSTANCE 变量已经被赋值,但单例对象并没有被完成初始化。

使用 volatile 关键字,可以保证在多线程环境下,单例对象的初始化过程是串行化的,从而避免了竞态条件。

5、volatile详解

在Java 语言中,volatile 关键字是一种轻量级的同步机制。它主要用于保证每个线程对于变量的读取都是最新的,保证了变量的可见性。

当一个线程对于一个volatile 变量进行修改时,Java 虚拟机会在该线程将修改后的值存回主存之前,强制将该线程中其他变量的缓存数据写回主存。而其他线程在访问该 volatile 变量时,将强制从主存中重新读取该变量的值,这样就能保证变量的可见性。

需要注意的是,volatile 关键字只能保证变量的可见性,不能保证原子性。

如果多个线程同时对同一个 volatile 变量进行修改,可能会出现竞态条件。而且volatile关键字不能替代锁机制来保证线程安全性。如果多个线程同时对一个变量进行修改,而该变量的状态是不确定的,那么就可能会出现问题。

另外, volatile 不能保证复合操作的原子性,如 i++ 不是原子性的操作,volatile 也无法保证原子性。

总结:使用volatile 保证了变量在多线程中可见性,但是不能保证原子性,还需要借助锁机制来实现线程安全。

6、volatile举例说明

当一个变量被volatile 修饰,它会告诉 JVM,这个变量可能会被多线程共享,因此需要进行特殊的处理。

下面是一个使用volatile 关键字的例子:

1. public class VolatileExample {
2. private volatile boolean flag = false;
3. public void setFlag(){
4. this.flag = true;
5.     }
6. public void printValue() {
7. if (flag) {
8.             System.out.println("flag is true");
9.         }
10.     }
11. }

在这个例子中,flag 是一个共享变量,可能会被多个线程修改。由于flag被volatile修饰,在这里保证多线程修改时读取到最新值。

常用于共享变量的内存可见性,如果没有volatile可能会出现每个线程读取到的变量值不同,或者其他线程不能及时获取到共享变量的最新值,这样会导致错误的结果或系统不稳定。

需要注意的是,volatile并不能保证原子性,如果需要保证变量的操作具有原子性,还需要配合使用synchronized或者Atomic类型。

此外,Java5以后引入了类java.util.concurrent.atomic.AtomicInteger, AtomicLong, AtomicBoolean, 等类型,它们都有一个显式的变量值和内存屏障(memory barrier),可以在多线程下保证变量更新的可见性和原子性,可以用来代替volatile.

在使用volatile关键字时,编译器与运行时会对它所修饰的变量的访问进行特殊的处理。

编译器会禁止重排序优化,会强制在每次读取该变量的时候,都直接从内存中获取最新的值。

这样可以确保在多线程环境下,所有线程都能够看到共享变量的最新值。

总的来说,使用volatile 关键字的目的是保证共享变量的内存可见性和禁止指令重排序,但不能保证变量操作的原子性,如果需要保证原子性可以使用 Atomic 类型或者加锁。

相关文章
|
NoSQL Java 数据库连接
SpringBoot快速入门 3
SpringBoot快速入门
280 0
|
JavaScript 前端开发 数据可视化
Jupyter Notebook如何调试?JupyterLab作为DeBug调试工具及调试教程
文章提供了JupyterLab debugger工具的安装和使用教程,包括如何在没有conda环境或已有conda环境下安装所需的软件包,如jupyterlab、Node.js、ptvsd和jupyterlab的debugger插件,以及如何使用debugger进行程序调试。同时,文章还列出了一些常见的安装问题及其解决办法。
4115 1
|
缓存 前端开发 JavaScript
深入了解rollup(三)插件机制
Rollup 插件是一个对象,具有属性]、构建钩子 和 输出生成钩子 中的一个或多个,并遵循我们的约定。插件应作为一个导出一个函数的包进行发布,该函数可以使用插件特定的选项进行调用并返回此类对象。 插件允许你通过例如在打包之前进行转译代码或在node_modules文件夹中查找第三方模块来自定义 Rollup 的行为。
385 1
|
数据可视化 数据挖掘 API
Python数据分析:数据可视化(Matplotlib、Seaborn)
数据可视化是数据分析中不可或缺的一部分,通过将数据以图形的方式展示出来,可以更直观地理解数据的分布和趋势。在Python中,Matplotlib和Seaborn是两个非常流行和强大的数据可视化库。本文将详细介绍这两个库的使用方法,并附上一个综合详细的例子。
|
缓存 运维 Linux
保姆级python项目离线部署服务器教程只需这一篇就够了(建议收藏)
这篇文章提供了详尽的Python项目在离线Linux(CentOS)服务器上的部署教程。作者首先介绍了环境背景,强调了无网络环境和使用有网络的CentOS虚拟机准备安装包的重要性。教程分为两部分:外网环境搭建和内网离线安装。在外网环境中,包括下载Python 3.9.0安装包、传输至服务器、安装依赖包,并使用pip3下载项目所需依赖。内网安装则涉及依赖包的复制和Python环境的同样步骤。最后,作者分享了运行项目的命令,并总结了离线安装的整个流程,提醒读者注意可能出现的问题。
保姆级python项目离线部署服务器教程只需这一篇就够了(建议收藏)
|
Java 测试技术 Python
《手把手教你》系列技巧篇(二十七)-java+ selenium自动化测试- quit和close的区别(详解教程)
【4月更文挑战第19天】本文介绍了WebDriver中关闭浏览器的两个方法:close和quit。close方法关闭当前窗口,如果这是最后一个窗口,浏览器也会退出。quit方法则直接退出浏览器并关闭所有关联窗口。示例代码展示了两者的区别,通常在自动化测试后使用quit来彻底关闭浏览器。close和quit在HTTP请求上的差异也进行了说明,close请求的是 `/session/{session id}/window/current`,而quit请求的是 `/session/{session id}`。
418 8
|
存储 NoSQL 关系型数据库
什么是列存储?一文秒懂
什么是列存储?一文秒懂
2528 1
|
数据处理 容器
RT-Thread快速入门-线程间同步之信号量
RT-Thread快速入门-线程间同步之信号量
601 0
|
API
JDK8函数式接口之Function
JDK8函数式接口之Function
439 0
|
存储 SQL 设计模式
C++面试题,阿里、百度、腾讯、华为、小米100道C++面试题目及答案
C++面试题,阿里、百度、腾讯、华为、小米100道C++面试题目及答案
1636 0

热门文章

最新文章