一、Bug 场景
在一个多线程的 Java 应用程序中,有一个任务需要对一个整数进行多次递增操作,并且在不同线程中并发执行该任务。开发人员认为简单的 i++ 操作能够正确地对整数进行递增,然而在实际运行过程中,却得到了与预期不符的结果,出现了一些 “灵异数值”,导致程序的业务逻辑出现错误。
二、代码示例
计数器类(有缺陷)
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
线程任务类
public class IncrementTask implements Runnable {
private Counter counter;
public IncrementTask(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}
测试代码
public class ConcurrencyBugExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new IncrementTask(counter));
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("预期结果应为 10000,实际结果: " + counter.getCount());
}
}
三、问题描述
- 预期行为:10 个线程每个线程执行 1000 次
count++操作,最终count的值应该为 10000。 - 实际行为:每次运行程序,得到的
count值都小于 10000,而且每次结果都不一致。这是因为i++操作在并发环境下不是原子操作。i++实际上包含了三个步骤:读取i的值、将值加 1、将加 1 后的值写回内存。在多线程环境下,当一个线程读取了i的值,但还未将加 1 后的值写回内存时,另一个线程也读取了i的值,这样就会导致两个线程对同一个值进行加 1 操作,从而出现数据竞争,最终结果小于预期值。
四、解决方案
- 使用
synchronized关键字:通过synchronized关键字来同步方法,确保同一时间只有一个线程能够执行increment方法。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
- 使用
AtomicInteger:AtomicInteger类提供了原子性的递增操作,能有效避免数据竞争问题。
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
- 使用
Lock接口:例如ReentrantLock,它提供了比synchronized更灵活的锁机制。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}