You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:00:54