Java 多线程下的可见性问题

一、Bug 场景

在一个实时监控系统中,多个线程负责收集不同传感器的数据,并将其汇总到一个共享变量中。同时,有一个主线程负责定期读取这个共享变量,将汇总的数据展示在监控界面上。

二、代码示例

java 复制代码
public class SensorData {
    private static int totalData;

    public static void updateData(int data) {
        totalData += data;
    }

    public static int getTotalData() {
        return totalData;
    }
}

public class SensorThread extends Thread {
    private int sensorId;

    public SensorThread(int sensorId) {
        this.sensorId = sensorId;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            int data = (int) (Math.random() * 100);
            SensorData.updateData(data);
            System.out.println("传感器 " + sensorId + " 更新数据: " + data);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class MonitoringSystem {
    public static void main(String[] args) {
        SensorThread[] sensorThreads = new SensorThread[3];
        for (int i = 0; i < sensorThreads.length; i++) {
            sensorThreads[i] = new SensorThread(i + 1);
            sensorThreads[i].start();
        }

        while (true) {
            try {
                Thread.sleep(1000);
                int total = SensorData.getTotalData();
                System.out.println("当前汇总数据: " + total);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

三、问题描述

  1. 预期行为:主线程能够实时获取到各个传感器线程更新后的最新汇总数据,并准确展示在监控界面上。
  2. 实际行为 :主线程读取到的 totalData 可能不是最新的。这是因为 Java 内存模型中,每个线程都有自己的工作内存,线程对共享变量的操作是在自己的工作内存中进行的,而不是直接操作主内存中的变量。当传感器线程更新 totalData 时,这个更新可能不会立即同步到主内存中,主线程从主内存读取 totalData 时,就可能读到旧的值。这种可见性问题会导致监控系统展示的数据不准确,无法实时反映传感器数据的变化。

四、解决方案

  1. 使用 volatile 关键字 :将共享变量 totalData 声明为 volatile,这样可以保证变量的可见性。volatile 关键字会强制线程每次读取变量时都从主内存中获取最新值,而不是从自己的工作内存中读取旧值。同时,当线程修改 volatile 变量时,会立即将修改同步到主内存中。
java 复制代码
public class SensorData {
    private static volatile int totalData;

    public static void updateData(int data) {
        totalData += data;
    }

    public static int getTotalData() {
        return totalData;
    }
}
  1. 使用 AtomicIntegerAtomicInteger 是 Java 提供的原子类,它内部使用了 CAS(Compare - and - Swap)操作来保证对变量的操作是原子性的,并且具有可见性。可以将 totalData 替换为 AtomicInteger 类型。
java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class SensorData {
    private static AtomicInteger totalData = new AtomicInteger(0);

    public static void updateData(int data) {
        totalData.addAndGet(data);
    }

    public static int getTotalData() {
        return totalData.get();
    }
}
相关推荐
rannn_1118 分钟前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习
qq_124987075311 分钟前
基于JavaWeb的大学生房屋租赁系统(源码+论文+部署+安装)
java·数据库·人工智能·spring boot·计算机视觉·毕业设计·计算机毕业设计
短剑重铸之日18 分钟前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
若鱼191940 分钟前
SpringBoot4.0新特性-Observability让生产环境更易于观测
java·spring
觉醒大王1 小时前
强女思维:着急,是贪欲外显的相。
java·论文阅读·笔记·深度学习·学习·自然语言处理·学习方法
努力学编程呀(๑•ี_เ•ี๑)1 小时前
【在 IntelliJ IDEA 中切换项目 JDK 版本】
java·开发语言·intellij-idea
码农小卡拉1 小时前
深入解析Spring Boot文件加载顺序与加载方式
java·数据库·spring boot
向上的车轮1 小时前
为什么.NET(C#)转 Java 开发时常常在“吐槽”Java:checked exception
java·c#·.net
Dragon Wu1 小时前
Spring Security Oauth2.1 授权码模式实现前后端分离的方案
java·spring boot·后端·spring cloud·springboot·springcloud
跳动的梦想家h1 小时前
环境配置 + AI 提效双管齐下
java·vue.js·spring