```java
public class Counter {
private static int count = 0;
public static void increment() {
count++;
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
increment();
}).start();
}
// 等待所有线程完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(最终计数: + count);
}
}
```
这段看似简单的Java代码实际上隐藏着一个危险的并发陷阱。表面上,它只是创建了1000个线程来递增一个计数器,但运行结果却常常令人困惑——输出的计数值很少能达到预期的1000。
陷阱根源:非原子性操作
问题出在`count++`这行看似无害的代码上。在Java中,递增操作实际上包含三个步骤:
1. 从内存读取count的当前值
2. 将值加1
3. 将新值写回内存
在多线程环境下,这些步骤可能被其他线程打断。假设count初始为0:
- 线程A读取count=0
- 线程B也读取count=0
- 线程A计算0+1=1并写入
- 线程B计算0+1=1并写入
- 结果count=1而不是2
解决方案
1. 使用synchronized关键字
```java
public static synchronized void increment() {
count++;
}
```
2. 使用AtomicInteger
```java
private static AtomicInteger count = new AtomicInteger(0);
public static void increment() {
count.incrementAndGet();
}
```
3. 使用Lock机制
```java
private static final Lock lock = new ReentrantLock();
public static void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
```
更深层次的教训
这个陷阱揭示了Java并发编程中的重要原则:
- 看似简单的操作在并发环境下可能变得复杂
- 共享变量的读写需要同步保护
- 不要依赖线程调度器的时序
- 测试时的不确定性可能掩盖问题
在现实项目中,这类问题往往更加隐蔽,可能在特定负载条件下才会显现,导致难以复现和调试的生产环境故障。理解这些反直觉的陷阱,是成为优秀Java开发者的必经之路。

被折叠的 条评论
为什么被折叠?



