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();
    }
}
相关推荐
bzmK1DTbd7 分钟前
SOLID原则在Java中的实践:单一职责与开闭原则
java·开发语言·开闭原则
winner888112 分钟前
C++ 命名空间、虚函数、抽象类、protected 权限全套通俗易懂精讲(附与 Java 对比)
java·开发语言·c++
直奔標竿23 分钟前
Java开发者AI转型第二十五课!Spring AI 个人知识库实战(四)——RAG来源追溯落地,拒绝AI幻觉
java·开发语言·人工智能·spring boot·后端·spring
qq_589568101 小时前
java基础学习,案例练习,即时通讯
java·开发语言·学习
逸Y 仙X1 小时前
文章十九: ElasticSearch Full Text 全文本查询
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
AI科技星1 小时前
全域数学·第卷:场计算机卷(场空间计算机)【乖乖数学】
java·开发语言·人工智能·算法·机器学习·数学建模·数据挖掘
0xDevNull1 小时前
Java泛型详解
java·开发语言·后端
嘻嘻哈哈樱桃1 小时前
牛客经典101题解题集--贪心算法+模拟
java·python·算法·贪心算法
AI进化营-智能译站1 小时前
ROS2 C++开发系列13-运算符重载让ROS2消息处理更自然
java·开发语言·c++·ai
shjita2 小时前
java根据键值对中值的大小进行排序的手法。
java·开发语言·servlet