《Java编程思想》忙等待并发练习出现NullPointerException排查
空指针根因定位
报错的NullPointerException和你以为的flag字段无关,问题出在Locker类的构造方法赋值逻辑:
- 你的构造方法代码里,参数名和类的成员变量完全同名,写的是
switcher = switcher,这行代码本质是把传入的局部参数自己赋值给自己,完全没有把参数值传递给Locker实例的成员变量。 - 类里定义的
private Switcher switcher;成员从来没有被赋过值,一直保持Java对象默认的null值。后续checkFlag()方法执行if(switcher.getFlag())的时候,对null对象调用实例方法,就直接抛出空指针了,和堆栈报错的位置完全匹配。
修复方案
- 构造方法里加
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); } - 补全题目要求的逻辑漏项:
你现在的代码检测到标志位为true后,只是把Locker自己维护的本地flag副本改成了false,根本没有修改Switcher里的共享标志位。要调用Switcher提供的setFlagToFalse()方法完成状态重置,同时把忙等待的耗时统计输出:
顺带可以删掉Locker里冗余的本地@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; } } }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
相关产品推荐
相关产品推荐

