diff --git a/public/api/i/2025/07/28/sd8zrp.webp b/public/api/i/2025/07/28/sd8zrp.webp old mode 100644 new mode 100755 diff --git a/public/api/i/2025/07/28/sfkvri.webp b/public/api/i/2025/07/28/sfkvri.webp old mode 100644 new mode 100755 diff --git a/public/api/i/2025/07/28/si308s.webp b/public/api/i/2025/07/28/si308s.webp old mode 100644 new mode 100755 diff --git a/public/api/i/2025/07/28/sj8wqs.webp b/public/api/i/2025/07/28/sj8wqs.webp old mode 100644 new mode 100755 diff --git a/public/api/i/2025/07/28/skj7xz.webp b/public/api/i/2025/07/28/skj7xz.webp old mode 100644 new mode 100755 diff --git a/public/api/i/cache/20250728.jpg b/public/api/i/cache/20250728.jpg old mode 100644 new mode 100755 diff --git a/public/api/i/cache/EasyImage_int_2ef4014b263d46a49d6b32e7f8f61a92.workcontrol.txt b/public/api/i/cache/EasyImage_int_2ef4014b263d46a49d6b32e7f8f61a92.workcontrol.txt old mode 100644 new mode 100755 diff --git a/public/api/i/cache/EasyImage_int_4e37e326dd17dc84bc60c328e2336c7d.workcontrol.txt b/public/api/i/cache/EasyImage_int_4e37e326dd17dc84bc60c328e2336c7d.workcontrol.txt old mode 100644 new mode 100755 diff --git a/public/api/i/cache/EasyImage_int_4e903caeabc91ab7b477ea724d4e26c6.workcontrol.txt b/public/api/i/cache/EasyImage_int_4e903caeabc91ab7b477ea724d4e26c6.workcontrol.txt old mode 100644 new mode 100755 diff --git a/public/api/i/cache/EasyImage_int_686a102ef7badc364c9c3f45490f6bb0.workcontrol.txt b/public/api/i/cache/EasyImage_int_686a102ef7badc364c9c3f45490f6bb0.workcontrol.txt old mode 100644 new mode 100755 diff --git a/public/api/i/cache/EasyImage_int_96c9e161dadad285b5fd41c38a278a09.workcontrol.txt b/public/api/i/cache/EasyImage_int_96c9e161dadad285b5fd41c38a278a09.workcontrol.txt old mode 100644 new mode 100755 diff --git a/src/content/posts/Java/JUC/volatile双重锁.md b/src/content/posts/Java/JUC/volatile双重锁.md new file mode 100644 index 0000000..2afd62f --- /dev/null +++ b/src/content/posts/Java/JUC/volatile双重锁.md @@ -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; + } +} + +``` diff --git a/src/content/posts/Java/JUC/线程安全单例.md b/src/content/posts/Java/JUC/线程安全单例.md new file mode 100644 index 0000000..860e220 --- /dev/null +++ b/src/content/posts/Java/JUC/线程安全单例.md @@ -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 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) \ No newline at end of file