多线程交替读取数组索引问题:notify/wait致线程全阻塞
问题分析
你遇到的核心问题是wait/notify的条件判断逻辑错误:
- 未用
while循环检查执行条件,仅用if判断,导致虚假唤醒后线程直接执行,破坏交替逻辑; - 缺少明确的状态标记区分当前执行权归属,导致两个线程均进入等待状态无法唤醒。
解决方案
核心思路是在SharedResource中维护一个状态变量(如isEvenTurn),标记当前是否轮到偶数索引线程执行。线程在同步块中通过while循环持续验证自身执行条件,不符合则调用wait();执行完成后切换状态并调用notifyAll()唤醒所有等待线程,让符合条件的线程继续执行。
修正后的核心代码
1. SharedResource类(含状态标记与数组)
public class SharedResource { private int[] array; private int currentIndex = 0; // 标记当前是否轮到偶数索引线程执行 private boolean isEvenTurn = true; public SharedResource(int size) { this.array = new int[size]; // 预先填充数组(模拟已完成插入操作) for (int i = 0; i < size; i++) { array[i] = i * 2; } } public synchronized void printIndexValue(boolean isEvenThread) { // 用while循环检查条件,避免虚假唤醒 while ((isEvenThread && !isEvenTurn) || (!isEvenThread && isEvenTurn)) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } // 检查是否还有可处理的索引 if (currentIndex >= array.length) { // 所有索引处理完毕,唤醒其他线程退出 isEvenTurn = !isEvenTurn; notifyAll(); return; } // 打印当前线程对应的索引值 String threadName = Thread.currentThread().getName(); System.out.printf("%s 处理索引 %d,值为 %d%n", threadName, currentIndex, array[currentIndex]); // 更新索引与执行状态 currentIndex++; isEvenTurn = !isEvenTurn; // 唤醒所有等待线程 notifyAll(); } // 供求和线程使用的方法 public synchronized int getSum() { int sum = 0; for (int num : array) { sum += num; } return sum; } // 暴露索引与数组长度,供线程判断退出条件 public synchronized int getCurrentIndex() { return currentIndex; } public int getArrayLength() { return array.length; } }
2. NumberGenerator类(单run方法实现奇偶交替)
public class NumberGenerator extends Thread { private SharedResource sharedResource; private boolean isEvenThread; public NumberGenerator(String name, SharedResource sharedResource, boolean isEvenThread) { super(name); this.sharedResource = sharedResource; this.isEvenThread = isEvenThread; } @Override public void run() { while (true) { sharedResource.printIndexValue(isEvenThread); // 检查所有索引是否处理完毕,退出线程 synchronized (sharedResource) { if (sharedResource.getCurrentIndex() >= sharedResource.getArrayLength()) { break; } } } } }
3. SumatoriaThread类(求和线程)
public class SumatoriaThread extends Thread { private SharedResource sharedResource; public SumatoriaThread(String name, SharedResource sharedResource) { super(name); this.sharedResource = sharedResource; } @Override public void run() { // 等待NumberGenerator线程完成所有索引处理 synchronized (sharedResource) { while (sharedResource.getCurrentIndex() < sharedResource.getArrayLength()) { try { sharedResource.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } } // 计算并打印总和 int sum = sharedResource.getSum(); System.out.printf("%s 计算得到数组总和:%d%n", getName(), sum); } }
4. 测试主类
public class Main { public static void main(String[] args) { SharedResource sharedResource = new SharedResource(10); // 数组大小设为10 // 启动2个NumberGenerator线程:H1处理偶数索引,H2处理奇数索引 NumberGenerator h1 = new NumberGenerator("H1", sharedResource, true); NumberGenerator h2 = new NumberGenerator("H2", sharedResource, false); // 启动2个SumatoriaThread线程 SumatoriaThread sum1 = new SumatoriaThread("Sum-1", sharedResource); SumatoriaThread sum2 = new SumatoriaThread("Sum-2", sharedResource); h1.start(); h2.start(); sum1.start(); sum2.start(); } }
关键注意点
- 用while而非if检查条件:wait被唤醒后必须重新验证条件,防止虚假唤醒导致线程错误执行;
- 统一状态标记:通过
isEvenTurn明确当前执行权归属,确保线程严格交替; - 使用notifyAll:避免只唤醒同类线程(如偶数线程唤醒另一个偶数线程),导致所有线程阻塞;
- 明确退出逻辑:当所有索引处理完毕后,线程需主动退出,避免无限循环。
内容的提问来源于stack exchange,提问作者VonMatterhorn
相关产品推荐
相关产品推荐

