Skip to content

Commit 2a19e80

Browse files
committed
fix: Issue#2650 - 修复CAS示例代码的并发打印问题和livelock
1 parent 6798a05 commit 2a19e80

File tree

1 file changed

+11
-60
lines changed

1 file changed

+11
-60
lines changed

docs/java/basis/unsafe.md

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -559,80 +559,31 @@ private void increment(int x){
559559
如果你把上面这段代码贴到 IDE 中运行,会发现并不能得到目标输出结果。有朋友已经在 Github 上指出了这个问题:[issue#2650](https://github.com/Snailclimb/JavaGuide/issues/2650)。下面是修正后的代码:
560560

561561
```java
562-
private volatile int a = 0; // 共享变量,初始值为 0
563-
private static final Unsafe unsafe;
564-
private static final long fieldOffset;
565-
566-
static {
567-
try {
568-
// 获取 Unsafe 实例
569-
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
570-
theUnsafe.setAccessible(true);
571-
unsafe = (Unsafe) theUnsafe.get(null);
572-
// 获取 a 字段的内存偏移量
573-
fieldOffset = unsafe.objectFieldOffset(CasTest.class.getDeclaredField("a"));
574-
} catch (Exception e) {
575-
throw new RuntimeException("Failed to initialize Unsafe or field offset", e);
576-
}
577-
}
578-
579-
public static void main(String[] args) {
580-
CasTest casTest = new CasTest();
581-
582-
Thread t1 = new Thread(() -> {
583-
for (int i = 1; i <= 4; i++) {
584-
casTest.incrementAndPrint(i);
585-
}
586-
});
587-
588-
Thread t2 = new Thread(() -> {
589-
for (int i = 5; i <= 9; i++) {
590-
casTest.incrementAndPrint(i);
591-
}
592-
});
593-
594-
t1.start();
595-
t2.start();
596-
597-
// 等待线程结束,以便观察完整输出 (可选,用于演示)
598-
try {
599-
t1.join();
600-
t2.join();
601-
} catch (InterruptedException e) {
602-
Thread.currentThread().interrupt();
603-
}
604-
}
605-
606562
// 将递增和打印操作封装在一个原子性更强的方法内
607563
private void incrementAndPrint(int targetValue) {
608564
while (true) {
609565
int currentValue = a; // 读取当前 a 的值
610-
// 只有当 a 的当前值等于目标值的前一个值时,才尝试更新
611-
if (currentValue == targetValue - 1) {
612-
if (unsafe.compareAndSwapInt(this, fieldOffset, currentValue, targetValue)) {
613-
// CAS 成功,说明成功将 a 更新为 targetValue
614-
System.out.print(targetValue + " ");
615-
break; // 成功更新并打印后退出循环
616-
}
617-
// 如果 CAS 失败,意味着在读取 currentValue 和执行 CAS 之间,a 的值被其他线程修改了,
618-
// 此时 currentValue 已经不是 a 的最新值,需要重新读取并重试。
566+
// 如果当前值已经达到或超过目标值,说明已被其他线程处理,跳过
567+
if (currentValue >= targetValue) {
568+
return;
619569
}
620-
// 如果 currentValue != targetValue - 1,说明还没轮到当前线程更新,
621-
// 或者已经被其他线程更新超过了,让出CPU给其他线程机会。
622-
// 对于严格顺序递增的场景,如果 current > targetValue - 1,可能意味着逻辑错误或死循环,
623-
// 但在此示例中,我们期望线程能按顺序执行。
624-
Thread.yield(); // 提示CPU调度器可以切换线程,减少无效自旋
570+
// 尝试 CAS 操作:如果当前值等于 targetValue - 1,则原子地设置为 targetValue
571+
if (unsafe.compareAndSwapInt(this, fieldOffset, currentValue, targetValue)) {
572+
// CAS 成功后立即打印,确保打印的就是本次设置的值
573+
System.out.print(targetValue + " ");
574+
return;
575+
}
576+
// CAS 失败,重新读取并重试
625577
}
626578
}
627579
```
628-
629580
在上述例子中,我们创建了两个线程,它们都尝试修改共享变量 a。每个线程在调用 `incrementAndPrint(targetValue)` 方法时:
630581

631582
1. 会先读取 a 的当前值 `currentValue`
632583
2. 检查 `currentValue` 是否等于 `targetValue - 1` (即期望的前一个值)。
633584
3. 如果条件满足,则调用`unsafe.compareAndSwapInt()` 尝试将 `a``currentValue` 更新到 `targetValue`
634585
4. 如果 CAS 操作成功(返回 true),则打印 `targetValue` 并退出循环。
635-
5. 如果 CAS 操作失败,或者 `currentValue` 不满足条件,则当前线程会继续循环(自旋),并通过 `Thread.yield()` 尝试让出 CPU,直到成功更新并打印或者条件满足
586+
5. 如果 CAS 操作失败,说明有其他线程同时竞争,此时会重新读取 `currentValue` 并重试,直到成功为止
636587

637588
这种机制确保了每个数字(从 1 到 9)只会被成功设置并打印一次,并且是按顺序进行的。
638589

0 commit comments

Comments
 (0)