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();
    }
}
相关推荐
疯狂敲代码的老刘几秒前
JDK 1.6到25 全版本网盘合集 (Windows + Mac + Linux)
java·linux·windows·macos·jdk
夕除2 分钟前
js--15
java·jvm·spring
曾经的三心草7 分钟前
redis-9-集群
java·redis·mybatis
sun032212 分钟前
【架构基础】Spring中的PropertySourcesPlaceholderConfigurer介绍 (并非新知识,比较古老的一种使用方式)
java·spring·架构
chilavert31815 分钟前
技术演进中的开发沉思-356:重排序(中)
java·开发语言
毕设源码-邱学长15 分钟前
【开题答辩全过程】以 基于SSM的儿童福利院管理系统为例,包含答辩的问题和答案
java·eclipse
TT哇21 分钟前
【实习】数字营销系统 银行经理端(interact_bank)前端 Vue 移动端页面的 UI 重构与优化
java·前端·vue.js·ui
Elieal31 分钟前
SpringBoot 数据层开发与企业信息管理系统实战
java·spring boot·后端
识君啊31 分钟前
MyBatis-Plus 逻辑删除导致唯一索引冲突的解决方案
java·spring boot·mybatis·mybatis-plus·唯一索引·逻辑删除
Coder_Boy_32 分钟前
Java开发者破局指南:跳出内卷,借AI赋能,搭建系统化知识体系
java·开发语言·人工智能·spring boot·后端·spring