多线程(55)如何使用volatile来保证可见性和有序性

在Java中,volatile关键字主要用于确保变量修改的可见性和操作的有序性。volatile提供了一种避免线程缓存变量副本的方式,确保每次访问变量时都从主内存中读取。

如何保证可见性

当一个字段被声明为volatile,JVM确保所有线程看到这个变量的值是一致的。即,当一个线程修改了这个变量的值,这个新值对于其他线程来说是立即可见的。这是通过在每次访问变量时不从线程的工作内存(本地内存)读取,而是直接从主内存读取,来实现的。

在底层,当CPU写入数据到一个被声明为volatile的变量时,会在写操作后插入一条内存屏障指令,这会使得所有缓存中的这个变量的副本失效,因此其他线程再访问这个变量时会直接从主内存中读取。

如何保证有序性

volatile还可以阻止JVM的指令重排序优化。指令重排序是一种提高执行效率的技术,但有时这种优化会导致在没有适当同步的多线程程序中出现问题。声明为volatile的变量,在写操作后和读操作前,JVM会插入特定类型的内存屏障指令来阻止特定类型的指令重排序,从而保证在volatile变量写操作之后的操作不会被重排序到写操作之前执行,保证了有序性。

示例代码

让我们通过一个简单的例子来演示volatile的可见性:

java 复制代码
public class VolatileExample {
    // 使用volatile关键字声明布尔型变量
    private volatile boolean running = true;

    public void startRunning() {
        new Thread(() -> {
            while (running) {
                // 某些操作
            }
            System.out.println("Thread is stopped.");
        }).start();
    }

    public void stopRunning() {
        running = false;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileExample example = new VolatileExample();
        example.startRunning();

        // 主线程休眠,确保上面的线程开始运行
        Thread.sleep(1000);

        // 修改running的值,使得上面的线程可以看到变化并停止运行
        example.stopRunning();
    }
}

在这个例子中,running变量被声明为volatile。如果没有volatile修饰,线程内循环可能看不到running变量值的改变,因为这个变量值的变化可能只在主内存中,而线程可能会一直使用CPU缓存中的旧值。使用volatile关键字后,任何对running变量的写操作立即对其他线程可见,从而确保了可见性。

源码层面解析

在JVM层面,volatile的实现依赖于内存屏障(Memory Barriers)。内存屏障是一种CPU指令,用于防止特定类型的操作在屏障之前和之后重排序。Java内存模型(JMM)通过在volatile变量的读写操作前后插入不同类型的内存屏障来防止指令重排序。

  • 写入volatile变量时,会在写操作后插入一个StoreStore屏障(确保在此之前的所有写操作完成)和一个StoreLoad屏障(防止后续任何读写操作提前发生)。
  • 读取volatile变量时,在读操作前插入一个LoadLoad屏障(确保后续读操作获取的是最新数据)和一个LoadStore屏障(确保读取操作不会和后续的写操作重排序)。

通过这种方式,volatile不仅保证了变量修改的可见性,还通过阻止指令重排保证了程序执行的有序性。

尽管volatile能够提供某种程度的线程安全,但它并不能解决所有并发问题。特别是,在涉及到多变量的复杂操作时,仍然需要使用更强大的同步机制(如synchronized或java.util.concurrent中的锁)来保证线程安全。

相关推荐
她的男孩20 分钟前
分片上传的大文件人人可下载:文件模块 isPrivate 在合并时被抹成 false,另有 4 个静默坑
java·后端·架构
颜进强27 分钟前
18 · NestJS动态模块与 forRoot:`imports: [ConfigModule.forRoot({...})]` 到底在 import 什么
前端·后端·ai编程
xn713333 分钟前
Personal AI Agent 架构实战:Memory、权限、跨 App 与本地/云端设计
人工智能·后端·agent
谢亮_vipxieliang43 分钟前
Go defer、panic与recover核心知识点
开发语言·后端·golang
颜进强1 小时前
17 · 自定义 Provider 四形态:useValue / useClass / useFactory / useExisting 到底在选什么
前端·后端·ai编程
碎觉崽1 小时前
我的 QQ 机器人被腾讯反复踢下线,折腾了半个月才搞明白
后端
打工仔折腾 AI1 小时前
从零写一个CAD 05:以鼠标为中心的滚轮缩放矩阵顺序不能反
后端·python·线性代数·性能优化·矩阵·计算机外设·ai agent 实战
谢亮_vipxieliang2 小时前
Go基础数据类型全解
开发语言·后端·golang
专业程序开发源2 小时前
springboot古城景区管理系统88564-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
小蒜学长2 小时前
基于微信小程序的遇见咖啡店点餐系统设计与实现(代码+数据库+LW)
java·spring boot·后端·微信小程序·咖啡店点餐系统