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();
    }
}
相关推荐
wuqingshun31415912 小时前
什么是责任链模式,一般用在什么场景?
java·责任链模式
:-)13 小时前
算法-归并排序
java·开发语言·数据结构·算法·排序算法
wuqingshun31415913 小时前
说一下消息队列的模型有哪些?
java
fīɡЙtīиɡ ℡14 小时前
布隆过滤器
java
yaoxin52112314 小时前
462. Java 反射 - 获取声明类与封闭类
java·开发语言·python
wuqingshun31415916 小时前
MYSQL的乐观锁和悲观锁是什么?
java
唐青枫17 小时前
Java SLF4J 实战指南:从日志门面到 Logback、MDC 和链路追踪
java
jvmind_dev18 小时前
Java GC 实战指南(番外篇):被忽视的隐形杀手 —— Class Unloading 如何拖垮 GC
java·后端
An_s18 小时前
机器学习python之识别图中物品信息
java·linux·开发语言
AIGS00118 小时前
跨越语义鸿沟:企业本体语义平台的构建与落地
java·人工智能·python·机器学习·人工智能ai大模型应用