文章提交

This commit is contained in:
2025-08-07 11:03:48 +00:00
parent 21f63f000b
commit 5f367f8c2e
13 changed files with 427 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
---
title: volatile-实现单例模式的双重锁
published: 2025-08-07
description: ''
image: ''
tags: [JUC,JAVA,volatile,双重锁]
category: 'Java > JUC'
draft: false
lang: ''
---
# 什么是单例模式的双重锁
单例模式的双重锁是一种实现单例模式的技术通过两次检查实例是否为null结合同步锁来保证在多线程环境下只创建一个实例并试图通过减少同步的次数来提高性能。为了确保线程安全尤其在涉及到对象创建的指令重排的问题的时候通常需要使用 `volatile`关键字来修饰单例类的实例变量。
# 非线程安全的单例模式
```java
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
> 多线程环境下,上面的简单实现在并发调用 `getInstance()`方法时候可能出现问题。
![](https://blog.meowrain.cn/api/i/2025/04/24/rajbl4-0.webp)
常见的做法是使用synchronized
```java
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
这种同步方式能确保线程安全因为在同一时间只有一个线程能够进入getInstance方法但是每次调用`getInstance`方法都需要获取锁,即使在实例已经创建之后也是如此,这样会带来额外的性能开销,尤其是在频繁调用`getInstance()`的情况下
# 什么是单例模式的双重检查锁定 -> 可能会导致半初始化问题
双重检查锁定就是为了保证在线程安全的前提下,尽量减少同步带来的性能开销
核心思想:
1. 第一次检查: 在进入同步块之前先检查insatnce是否为null如果不是null说明实例已经创建可以直接返回避免进入同步块。
2. 同步块: 如果第一次检查发现instance是null就进入同步块
3. 第二次检查: 在同步块内再次检查instance是否为null这是至关重要的一部因为可能多个线程都通过了第一次检查但只有一个线程进入同步块在同步块内再次检查可以确保只有一个线程会智行对象的创建操作。
4. 创建实例如果第二次检查发现instance仍然为null才真正创建对象并把引用赋值给instance
```java
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
> 尽管双重检查锁看起来聪明地减少了同步磁化但是在JMM(JAVA 内存模型)种,没有使用`volatile`的双重检查锁仍然存在`指令重排`的问题。
对象创建的过程 `instance = new SimpleSingleton();` 实际上能分解为三个步骤:
1. 为对象分配内存空间
2. 初始化对象
3. 将分配的内存空间的地址赋值给`instance`变量
在某些情况下JVM为了优化性能可能会对这三个步骤进行重排序例如可能会将步骤三排在步骤2之前
![](https://blog.meowrain.cn/api/i/2025/04/24/s9qvuj-0.webp)
# 为什么用volatile
1. 可见性: volatile确保了所有线程都能看到instance变量的最新值当一个线程修改了instance值这个改变会立即对其他线程可见。
2. 禁止指令重排解决了半初始化的问题确保instance变量被赋值为非null之前对象已经被完全初始化。
```java
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;
}
}
```

View File

@@ -0,0 +1,308 @@
---
title: 线程安全单例
published: 2025-08-07
description: ''
image: ''
tags: [JUC,JAVA,volatile,线程安全,单例模式]
category: 'Java > JUC'
draft: false
lang: ''
---
![](https://blog.meowrain.cn/api/i/2025/05/27/11a6ta3-0.webp)
# 1 解决反序列化导致的单例破坏现象
这里的单例问题是如果对一个可序列化对象进行反序列化会创建一个新的对象这就违背了我们想要全局单例的目标。因此要重写readResolve方法。
![](https://blog.meowrain.cn/api/i/2025/05/26/sjvikb-0.webp)
```java
package org.example.sigletons;
import java.io.Serializable;
public class Singleton1 implements Serializable {
private Singleton1(){}
private static final Singleton1 INSTANCE = new Singleton1();
public Singleton1 getInstance() {
return INSTANCE;
}
public Object readResolve() {
return INSTANCE;
}
}
```
# 2 使用枚举实现单例模式
![](https://blog.meowrain.cn/api/i/2025/05/26/swwpe2-0.webp)
```java
package org.example.sigletons;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public enum Singleton2 {
INSTANCE;
private final Properties properties;
private Singleton2() {
properties = new Properties();
String configFile = "application.properties";
System.out.println("ConfigurationManager: Initializing and loading " + configFile);
try(InputStream inputStream = Singleton2.class.getClassLoader().getResourceAsStream(configFile)){
if(inputStream == null){
System.out.println("ConfigurationManager: Sorry, unable to find " + configFile);
// 在实际应用中,这里可能抛出异常或有更复杂的错误处理
return;
}
properties.load(inputStream);
System.out.println("ConfigurationManager: Configuration loaded successfully.");
}catch (IOException e) {
e.printStackTrace();
}
}
public String getProperty(String key) {
return properties.getProperty(key);
}
public String getProperty(String key,String defaultValue) {
return properties.getProperty(key, defaultValue);
}
// 可以添加其他需要的方法,比如重新加载配置等(需要考虑线程安全)
public void listProperties() {
properties.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
class TestSingleton2 {
public static void main(String[] args) {
Singleton2 singleton2 = Singleton2.INSTANCE;
singleton2.listProperties();
}
}
```
![](https://blog.meowrain.cn/api/i/2025/05/26/swav0i-0.webp)
💎 枚举单例:全面解答这些问题!📜✨
枚举单例是一种非常推荐的单例实现方式,因为它不仅简单、易用,还天然地具备线程安全和防止反序列化、反射破坏单例的能力。接下来,我们重点针对 **枚举单例** 来回答这些问题!
---
### **问题 1枚举单例是如何限制实例个数的**
枚举单例通过枚举的机制天然地保证:
1. 枚举类的每一个枚举实例(如单例对象)都在 **类加载阶段** 就完成初始化,并且整个应用程序中只有一个实例。
2. 枚举类型底层由 JVM 的实现机制保证,它不像普通类那样允许通过反射或 `new` 额外创建实例。
#### 示例:
```java
public enum SingletonEnum {
INSTANCE; // 枚举单例实例
public void doSomething() {
System.out.println("Doing something...");
}
}
```
#### 使用方式:
即便通过 `SingletonEnum.INSTANCE` 多次获取,得到的始终是同一个实例。
```java
SingletonEnum instance1 = SingletonEnum.INSTANCE;
SingletonEnum instance2 = SingletonEnum.INSTANCE;
System.out.println(instance1 == instance2); // 输出true
```
---
### **问题 2枚举单例在创建时是否有并发问题**
枚举单例天然线程安全因为
1. 枚举类型的初始化由 JVM 保证是在类加载时完成的
2. 类加载过程是线程安全的JVM 使用了类加载的同步机制保证枚举单例的初始化不会因多线程而发生竞争
#### 举例
即使多个线程同时调用 `SingletonEnum.INSTANCE`它们都会得到在类加载阶段构造好的唯一对象无需额外同步
---
### **问题 3枚举单例能否被反射破坏单例**
**不会** 枚举类型的结构特殊无法被反射破坏单例这是因为
1. 枚举的构造器是私有的并且其底层会检测反射调用
2. 如果尝试通过反射显式调用枚举类的构造器JVM 会抛出 `IllegalArgumentException`
#### 验证代码
```java
import java.lang.reflect.Constructor;
public class EnumReflectionTest {
public static void main(String[] args) {
try {
// 获取枚举的构造器
Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonEnum instance = constructor.newInstance(); // 反射创建枚举对象
} catch (Exception e) {
e.printStackTrace(); // 会抛出 IllegalArgumentException
}
}
}
```
#### 运行结果:
```
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
```
---
### **问题 4枚举单例能否被反序列化破坏单例**
枚举单例天然具备防止反序列化破坏的特性,原因是:
1. `Enum` 类型的序列化机制是由 JVM 内部实现的,不走普通的对象序列化流程。
2. 反序列化枚举对象时JVM 会直接返回枚举类中的现有实例,而不是从序列化流中创建新对象。
#### 验证代码:
```java
import java.io.*;
public class EnumSerializationTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingletonEnum instance1 = SingletonEnum.INSTANCE;
// 序列化枚举对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enum_singleton.obj"));
oos.writeObject(instance1);
oos.close();
// 反序列化枚举对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enum_singleton.obj"));
SingletonEnum instance2 = (SingletonEnum) ois.readObject();
// 判断是否破坏单例
System.out.println(instance1 == instance2); // 输出true单例没有破坏
}
}
```
---
### **问题 5枚举单例属于懒汉式还是饿汉式**
**枚举单例本质上属于饿汉式单例**。它的特点是**在类加载阶段完成初始化**
- 枚举的实例在类加载时就被创建并初始化。
- 即使程序中从未访问过 `SingletonEnum.INSTANCE`,枚举实例依然会被加载。
#### 优点:
- 线程安全,无需为单例初始化额外编写同步代码。
- 实现简洁JVM 自动保证。
#### 缺点:
- 如果枚举实例较多,并且包含较大的初始化逻辑,会导致类加载阶段性能开销增加。
---
### **问题 6枚举单例如果希望加入一些初始化逻辑该如何做**
可以通过添加枚举的构造方法和静态方法来实现初始化逻辑。枚举的构造方法是私有的,可以用来在实例创建时执行初始化。
#### 修改代码:
```java
public enum SingletonEnum {
INSTANCE; // 枚举单例实例
private String configuration;
// 枚举的构造方法
SingletonEnum() {
// 初始化逻辑
configuration = "System Configuration Loaded";
}
public String getConfiguration() {
return configuration;
}
}
```
#### 测试:
```java
public class TestEnumInitialization {
public static void main(String[] args) {
SingletonEnum instance = SingletonEnum.INSTANCE;
System.out.println(instance.getConfiguration()); // 输出System Configuration Loaded
}
}
```
#### 分析:
- 枚举类型的构造器会在类加载时调用,且只调用一次。
- 可用枚举构造器实现单例实例的初始化逻辑。
---
### 总结
**为何枚举单例完美适合单例模式?**
- 它是天生线程安全的JVM 保障了枚举实例的唯一性。
- 枚举实例不能通过反射或序列化破坏。
- 枚举的初始化流程天然符合饿汉式单例的特点。
---
# 3 Double Check
https://meowrain.cn/archives/volatile-shi-xian-dan-li-mo-shi-de-shuang-zhong-suo
```java
package cn.meowrain;
public class DoubleSingleton {
private static volatile DoubleSingleton INSTANCE = null;
public static DoubleSingleton getInstance() {
if(INSTANCE != null) {
return INSTANCE;
}
synchronized (DoubleSingleton.class){
if(INSTANCE != null) {
return INSTANCE;
}
INSTANCE = new DoubleSingleton();
return INSTANCE;
}
}
}
```
![](https://blog.meowrain.cn/api/i/2025/05/27/10zdcs1-0.webp)
# 4 静态内部类懒汉式创建线程安全单例
```
package cn.meowrain;
public class Singleton2 {
private Singleton2(){}
// 问题1 属于懒汉式还是饿汉式
private static class LazyLoader{
static final Singleton2 INSTANCE = new Singleton2();
}
// 在创建的时候是否有并发问题
public static Singleton2 getInstance() {
return LazyLoader.INSTANCE;
}
}
```
![](https://blog.meowrain.cn/api/i/2025/05/27/1132c75-0.webp)
![](https://blog.meowrain.cn/api/i/2025/05/27/1144xf0-0.webp)