文章提交
0
public/api/i/2025/07/28/sd8zrp.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
0
public/api/i/2025/07/28/sfkvri.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
0
public/api/i/2025/07/28/si308s.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
0
public/api/i/2025/07/28/sj8wqs.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
0
public/api/i/2025/07/28/skj7xz.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
0
public/api/i/cache/20250728.jpg
vendored
Normal file → Executable file
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 320 KiB |
0
public/api/i/cache/EasyImage_int_2ef4014b263d46a49d6b32e7f8f61a92.workcontrol.txt
vendored
Normal file → Executable file
0
public/api/i/cache/EasyImage_int_4e37e326dd17dc84bc60c328e2336c7d.workcontrol.txt
vendored
Normal file → Executable file
0
public/api/i/cache/EasyImage_int_4e903caeabc91ab7b477ea724d4e26c6.workcontrol.txt
vendored
Normal file → Executable file
0
public/api/i/cache/EasyImage_int_686a102ef7badc364c9c3f45490f6bb0.workcontrol.txt
vendored
Normal file → Executable file
0
public/api/i/cache/EasyImage_int_96c9e161dadad285b5fd41c38a278a09.workcontrol.txt
vendored
Normal file → Executable file
119
src/content/posts/Java/JUC/volatile双重锁.md
Normal 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()`方法时候可能出现问题。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
常见的做法是使用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之前
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# 为什么用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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
308
src/content/posts/Java/JUC/线程安全单例.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
---
|
||||||
|
title: 线程安全单例
|
||||||
|
published: 2025-08-07
|
||||||
|
description: ''
|
||||||
|
image: ''
|
||||||
|
tags: [JUC,JAVA,volatile,线程安全,单例模式]
|
||||||
|
category: 'Java > JUC'
|
||||||
|
draft: false
|
||||||
|
lang: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
# 1 解决反序列化导致的单例破坏现象
|
||||||
|
|
||||||
|
这里的单例问题是,如果对一个可序列化对象进行反序列化,会创建一个新的对象,这就违背了我们想要全局单例的目标。因此要重写readResolve方法。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```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 使用枚举实现单例模式
|
||||||
|

|
||||||
|
|
||||||
|
```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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
💎 枚举单例:全面解答这些问题!📜✨
|
||||||
|
|
||||||
|
枚举单例是一种非常推荐的单例实现方式,因为它不仅简单、易用,还天然地具备线程安全和防止反序列化、反射破坏单例的能力。接下来,我们重点针对 **枚举单例** 来回答这些问题!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **问题 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||