You need to enable JavaScript to run this app. 《Java编程思想》忙等待并发练习出现NullPointerException排查
优惠活动
大模型
产品
解决方案
定价
更多

《Java编程思想》忙等待并发练习出现NullPointerException排查

空指针根因定位

报错的NullPointerException和你以为的flag字段无关,问题出在Locker类的构造方法赋值逻辑:

  • 你的构造方法代码里,参数名和类的成员变量完全同名,写的是switcher = switcher,这行代码本质是把传入的局部参数自己赋值给自己,完全没有把参数值传递给Locker实例的成员变量。
  • 类里定义的private Switcher switcher;成员从来没有被赋过值,一直保持Java对象默认的null值。后续checkFlag()方法执行if(switcher.getFlag())的时候,对null对象调用实例方法,就直接抛出空指针了,和堆栈报错的位置完全匹配。
修复方案
  1. 构造方法里加this关键字明确区分成员变量和局部参数,完成正确赋值:
    public Locker(Switcher switcher) {
        // this.switcher指向当前实例的成员变量,不再和局部参数混淆
        this.switcher = switcher;
        this.flag = switcher.getFlag();
        System.out.println("Class Switcher: " + switcher);
        System.out.println("field flag: " + flag);
    }
    
  2. 补全题目要求的逻辑漏项:
    你现在的代码检测到标志位为true后,只是把Locker自己维护的本地flag副本改成了false,根本没有修改Switcher里的共享标志位。要调用Switcher提供的setFlagToFalse()方法完成状态重置,同时把忙等待的耗时统计输出:
    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while (true) {
            checkFlag();
            if (flag) {
                // 直接修改共享对象的标志位,不是改本地副本
                switcher.setFlagToFalse();
                long duration = System.currentTimeMillis() - start;
                System.out.println("忙等待累计耗时:" + duration + "ms,标志位已重置为false");
                return;
            }
        }
    }
    
    顺带可以删掉Locker里冗余的本地flag字段,直接读取Switcher里的volatile标志位即可,减少不必要的状态维护。
wait()替代版参考实现

不需要循环占CPU的等待版本,利用对象内置锁的等待/通知机制实现:

// 带等待逻辑的任务类
class WaitLocker implements Runnable {
    private final Switcher switcher;
    public WaitLocker(Switcher switcher) {
        this.switcher = switcher;
    }
    @Override
    public void run() {
        long start = System.currentTimeMillis();
        try {
            synchronized (switcher) {
                // 循环判断等待条件,避免虚假唤醒
                while (!switcher.getFlag()) {
                    switcher.wait(); // 释放对象锁,进入阻塞等待,不消耗CPU
                }
                switcher.setFlagToFalse();
                long duration = System.currentTimeMillis() - start;
                System.out.println("wait阻塞等待累计耗时:" + duration + "ms,标志位已重置为false");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 修改Switcher,设置标志位后唤醒等待线程
class Switcher implements Runnable {
    private volatile boolean flag = false;
    @Override
    public void run() {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        synchronized (this) {
            flag = true;
            notifyAll(); // 唤醒所有在当前对象上等待的线程
        }
    }
    public synchronized boolean getFlag() { return flag; }
    public void setFlagToFalse() {
        flag = false;
        System.out.println("当前共享标志位状态:" + flag);
    }
    @Override
    public String toString() {
        return "Switcher当前状态:" + flag;
    }
}

内容的提问来源于stack exchange,提问作者Nikolay Nickolskiy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 22:57:02