目的
- 验证 i++ 的非原子性
环境配置
- idea中下载插件
jclasslib
用于查看字节码
测试 i++ 是否存在并发安全
代码Demo
- 开十个线程,每个线程执行 i++ 1万次,看最后 结果 是否是 10万
public class AtomicTest {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
// 运行 10 个线程
for (int j = 0; j < 10; j++) {
Thread thread = new Thread(() -> {
// 每个线程 加 1万次
for (int k = 0; k < 10000; k++) {
i++;
}
});
thread.start();
}
// 等2秒,所有线程跑完,查看最终结果
Thread.sleep(2000);
System.out.println("i = " + i);
}
}
运行结果
- 结果随机,不是 10万
原因分析
-
查看字节码
-
通过
jclasslib
查看字节码可知,在 jvm 层面,i++
可以分解为多个子操作,大概包括:取值,加一,设置值
。由于这几个操作整体的非原子性,导致可能多个线程同时 加一,最后互相覆盖值,使得最终的结果不够 10万。
解法一:使用 synchronized 加锁
- 对
i++
操作用synchronized
加锁,修改后的代码
public class AtomicTest {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
// 运行 10 个线程
for (int j = 0; j < 10; j++) {
Thread thread = new Thread(() -> {
// 每个线程 加 1万次
for (int k = 0; k < 10000; k++) {
synchronized (AtomicTest.class) {
i++;
}
}
});
thread.start();
}
// 等2秒,所有线程跑完,查看最终结果
Thread.sleep(2000);
System.out.println("i = " + i);
}
}
- 查看字节码
- 看到了熟悉了
monitorenter
和monitorexit
,synchronized
是在 jvm 层面的加锁,释放锁