双重检查锁单例

简介: 双重检查锁单例

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 类型或者加锁。

相关文章
|
3月前
单列模式【饿汉式和懒汉式】
单列模式【饿汉式和懒汉式】
28 0
|
设计模式 存储
static应用之 单例设计模式(饿汉单例&懒汉单例)
本章我们来学习单例模式中的饿汉单例和懒汉单例,那么什么是单例模式呢?应用该模式的这个类永远只有一个实列,即一个类只能创建一个对象例如电脑上的任务管理器对象只需要一个就能解决问题,可以节省内存空间先定义一个类,把构造器私有如下图,先来看一下没有把构造器私有化的SingleInstance类,此时Test类中可以随意创建多个SingleInstance的实例化。 在SingleInstance类中用private修饰无参构造器,此时左边new方法报错了。我们在右边创建一个静态变量来存储对象,变量名为instan
75 0
|
8月前
|
设计模式 安全 Java
单例模式:饿汉模式、懒汉模式
单例模式:饿汉模式、懒汉模式
121 0
|
7月前
|
安全 Java
线程安全的单例模式(Singleton)
线程安全的单例模式(Singleton)
|
8月前
|
SQL 安全 Java
懒汉式单例的3个坑
懒汉式单例的3个坑
|
5月前
|
设计模式 安全 Java
Singleton 的双重检查锁定
【8月更文挑战第23天】
54 2
|
8月前
|
安全 Java 编译器
单例模式之饿汉模式&懒汉模式
单例模式之饿汉模式&懒汉模式
|
安全
线程安全的单例懒汉式
线程安全的单例懒汉式
50 0
|
安全 Java
懒汉式单例
懒汉式单例
109 0
|
安全 Java
饿汉式单例
饿汉式单例

热门文章

最新文章