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();
    }
}
相关推荐
汤姆yu2 分钟前
基于springboot的热门文创内容推荐分享系统
java·spring boot·后端
星光一影3 分钟前
教育培训机构消课管理系统智慧校园艺术舞蹈美术艺术培训班扣课时教务管理系统
java·spring boot·mysql·vue·mybatis·uniapp
lkbhua莱克瓦246 分钟前
MySQL介绍
java·开发语言·数据库·笔记·mysql
武昌库里写JAVA8 分钟前
在iview中使用upload组件上传文件之前先做其他的处理
java·vue.js·spring boot·后端·sql
董世昌4112 分钟前
什么是事件冒泡?如何阻止事件冒泡和浏览器默认事件?
java·前端
好度18 分钟前
配置java标准环境?(详细教程)
java·开发语言
teacher伟大光荣且正确23 分钟前
关于Qt QReadWriteLock(读写锁) 以及 QSettings 使用的问题
java·数据库·qt
nightseventhunit25 分钟前
base64字符串String.getByte导致OOM Requested array size exceeds VM limit
java·oom
悟能不能悟42 分钟前
java map判断是否有key,get(key)+x,否则put(key,x)的新写法
java·开发语言
webbodys1 小时前
Python文件操作与异常处理:构建健壮的应用程序
java·服务器·python