JUC并发编程-volatile

目录

1. volatile的两大特性

可见性(Visibility)

有序性(Ordering)

2. 四大屏障

3. 读写屏障插入策略

happens-before与volatile变量规则:

注意事项

4. 原子性

5. 禁重排

6.使用场景

传统的单例模式实现如下:(多线程环境下会出问题)

DCL单例模式实现


1. volatile的两大特性

可见性(Visibility)

  • 当一个线程修改了一个volatile变量时,新值会立即被写入主内存,并且其他线程读取该变量时会从主内存中重新读取最新值。这确保了volatile变量的修改对其他线程立即可见。

有序性(Ordering)

  • volatile可以禁止对声明为volatile的变量前后的操作进行指令重排序优化。即编译器和处理器不会对volatile变量前后的操作进行重排序,保证代码的执行顺序与程序代码顺序相同。

2. 四大屏障

  • LoadLoad屏障

    • 作用:在Load1之后和Load2之前插入LoadLoad屏障,确保Load1数据的读取操作在Load2及后续读取操作之前完成。
    • 目的:防止处理器将后面的读操作提前到前面的读操作之前。
  • StoreStore屏障

    • 作用:在Store1之后和Store2之前插入StoreStore屏障,确保Store1的写入操作在Store2及后续写入操作之前完成。
    • 目的:防止处理器将后面的写操作提前到前面的写操作之前。
  • LoadStore屏障

    • 作用:在Load操作之后和Store操作之前插入LoadStore屏障,确保Load操作的数据读取在Store操作的数据写入之前完成。
    • 目的:防止处理器将写操作提前到读操作之前。
  • StoreLoad屏障

    • 作用:在Store操作之后和Load操作之前插入StoreLoad屏障,确保Store操作的数据写入在Load操作的数据读取之前完成。
    • 目的:这是所有屏障中最强大的一个,它不仅保证了屏障前后的读写操作的顺序,还保证了屏障前的写操作对屏障后的读操作可见。

volatile变量的读写场景中,具体会用到以下屏障:

  • 读操作

    • 在读取volatile变量之前,会插入一个LoadLoad屏障和一个LoadStore屏障,用来防止读取操作被重排序。
  • 写操作

    • 在写入volatile变量之后,会插入一个StoreStore屏障和一个StoreLoad屏障,用来确保写入操作对其他线程立即可见。

这些内存屏障保证了volatile变量在多线程环境下的可见性和有序性,是JVM底层对Java内存模型(JMM)的一种实现。需要注意的是,这些屏障的实现细节依赖于具体的CPU架构和JVM的版本。

3. 读写屏障插入策略

happens-before与volatile变量规则:

第一个操作第二个操作:普通读写第二个操作:volatile读第二个操作:volatile写
普通读写可以重排可以重排不可以重排
volatile读不可以重排不可以重排不可以重排
volatile写可以重排不可以重排不可以重排

注意事项

  • 屏障的顺序性:屏障确保了操作的顺序性,防止了重排序的发生,这对于维护多线程之间的内存可见性至关重要。
  • 性能影响:由于屏障会阻止指令的重排序,这可能会对程序的性能产生一定的影响。因此,应该只在确实需要保证可见性和有序性的情况下使用volatile变量。
  • JVM和CPU架构差异:不同的JVM实现和不同的CPU架构可能会对屏障的实现有所不同,但它们都必须遵守Java内存模型(JMM)的规范。

4. 原子性

volatile的变量复合操作不具有原子性

在多线程环境中,如果你需要一个复合操作具有原子性,仅仅使用volatile是不够的。你需要使用锁或其他原子操作机制来确保操作的原子性。

5. 禁重排

为了防止指令重排序,可以使用volatile关键字、锁(如synchronized)这些机制来禁止重排序。

volatile只是确保了可见性,但是无法保证多个线程可能会同时读取同一个volatile变量的值,然后基于这个值进行计算,并写回新值,导致结果不正确。需要通过加锁的方式解决。

6.使用场景

  • 单一赋值例如i=10这种操作可以,但是复合运算例如类似i++这种操作不可以
  • 状态标识,判断业务是否结束
  • 开销较低的读操作,写读策略
  • DCL双端锁的发布

DCL(Double-Checked Locking)是一种优化单例(Singleton)模式的方法,旨在减少创建单例对象时同步代码块的开销。在传统的单例模式实现中,为了保证单例的唯一性,通常会使用一个同步块来确保在多线程环境中只有一个线程能够创建实例。

传统的单例模式实现如下:(多线程环境下会出问题)

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

DCL单例模式实现

DCL单例模式试图通过减少同步块的使用来优化性能。它的核心思想是先检查实例是否已经创建,如果没有创建,再使用同步块来创建实例。这样可以避免在实例已经创建的情况下多次执行同步块。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

探索星辰大海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值