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

多线程交替读取数组索引问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 06:40:53