为何volatile关键字未按预期生效?附可见性验证实验代码
问题描述
为验证volatile的可见性,我编写了Java测试代码,通过wait/notify机制让两个线程交替执行:线程1循环执行i++操作后唤醒线程2,线程2先读取i的值(before),等待唤醒后再次读取i的值(after),但输出中多次出现before与after相等的情况,最终i值还出现异常。
测试代码
package org.example; public class Main3 { private static volatile int i = 0; private static Object o = new Object(); private static void add() { i++; } public static void main(String[] args) throws InterruptedException { // 执行写操作的线程 Thread th1 = new Thread( () -> { for (int j = 0; j < 10000; ) { synchronized (o) { add(); o.notify(); } } } ); // 执行读操作的线程 Thread th2 = new Thread( () -> { for (int j = 0; j < 10000; ) { int before = i; synchronized (o) { try { o.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } int after = i; if( before == after ) { System.out.println( "before: " + before + ", after: " + after ); } } } ); th1.start(); th2.start(); th1.join(); th2.join(); System.out.println(i); } }
输出结果
before: 1201480699, after: 1201480699 before: 1220677165, after: 1220677165 before: 1791815757, after: 1791815757 before: 1807949964, after: 1807949964 before: 1940818428, after: 1940818428 before: -1687188478, after: -1687188478 before: -1462842855, after: -1462842855
我原本期望线程2能感知到线程1对i的修改,但实际未达预期,请问为何volatile关键字未按预期生效?
问题分析与解决
你的代码问题和volatile的可见性无关,完全是线程同步逻辑和循环条件的漏洞导致执行偏离预期:
1. 循环条件未递增,线程陷入无限循环
两个线程的for循环里,j变量始终没有执行j++,这意味着线程会无限执行循环体,不会在10000次后停止。线程1会疯狂执行i++,直到int类型溢出变为负数,这就是你看到最终i值异常的直接原因。
2. wait/notify执行顺序完全失控
你想要的“交替执行”逻辑完全没有实现:
- 线程2可能还没调用
wait()时,线程1已经多次调用notify(),这些信号会直接丢失(没有线程在等待时,notify()没有任何效果)。 - 当线程2进入
wait()后,线程1可以立刻重新获取锁,连续执行N次i++,完全没有等待线程2的读取操作。线程2的before是等待前读取的旧值,after是等待后读取的新值,但如果线程调度时机巧合,可能出现before和after读取的是线程1连续多次修改后的同一个值。 wait()会释放锁,线程1在notify()后可以立刻再次抢占锁,导致线程1连续执行多次add(),完全破坏了交替执行的逻辑。
3. i++的原子性误区
即使加了volatile,i++也不是原子操作——它包含读取i、加1、写回i三个独立步骤。虽然这里线程1是单线程写,暂时不会出现并发写的问题,但如果是多线程写场景,volatile无法保证原子性,必须配合synchronized或原子类(比如AtomicInteger)。
修正后的代码示例
要实现“线程1执行一次i++,线程2感知变化并对比前后值”的逻辑,需要严格控制线程执行顺序,同时修正循环条件:
package org.example; public class Main3 { private static volatile int i = 0; private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { // 写线程:执行10000次i++,每次完成后等待读线程确认 Thread writer = new Thread(() -> { for (int j = 0; j < 10000; j++) { synchronized (lock) { i++; lock.notify(); // 唤醒读线程读取 try { if (j < 9999) { // 最后一次无需等待,避免死锁 lock.wait(); // 等待读线程完成读取 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }); // 读线程:每次等待写线程完成i++后,对比前后值 Thread reader = new Thread(() -> { for (int j = 0; j < 10000; j++) { synchronized (lock) { int before = i; try { if (j == 0) { lock.wait(); // 第一次等待写线程完成首次i++ } else { lock.notify(); // 唤醒写线程继续执行 lock.wait(); // 等待写线程完成下一次i++ } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } int after = i; if (before == after) { System.out.println("before: " + before + ", after: " + after); } } } }); reader.start(); writer.start(); reader.join(); writer.join(); System.out.println("最终i的值:" + i); } }
总结
volatile在你的代码里是生效的,问题出在同步逻辑错误导致线程执行顺序完全失控。- 要实现线程交替执行,必须让每个线程完成操作后等待对方的信号,而非单方面唤醒后继续执行。
- 循环条件必须正确递增,避免无限循环导致的变量溢出和资源浪费。
内容的提问来源于stack exchange,提问作者Albin Chang
相关产品推荐
相关产品推荐

