一、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();
}
}
}
}
三、问题描述
- 预期行为:主线程能够实时获取到各个传感器线程更新后的最新汇总数据,并准确展示在监控界面上。
- 实际行为 :主线程读取到的
totalData可能不是最新的。这是因为 Java 内存模型中,每个线程都有自己的工作内存,线程对共享变量的操作是在自己的工作内存中进行的,而不是直接操作主内存中的变量。当传感器线程更新totalData时,这个更新可能不会立即同步到主内存中,主线程从主内存读取totalData时,就可能读到旧的值。这种可见性问题会导致监控系统展示的数据不准确,无法实时反映传感器数据的变化。
四、解决方案
- 使用
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;
}
}
- 使用
AtomicInteger:AtomicInteger是 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();
}
}