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

Java多线程:如何用wait()和notify()获取单线程结果并终止另一线程

解决两个线程抢先返回结果并终止另一个的问题

你的需求本质是获取第一个完成的异步任务结果,同时终止未完成的任务,手动用wait()/notify()容易踩坑,直接用Java并发包提供的工具类就能完美解决,不用自己造轮子。

核心方案:ExecutorService.invokeAny()

ExecutorService.invokeAny()会提交一组Callable任务,返回第一个成功完成的任务结果,并且自动中断(取消)其他还在运行的任务,完全匹配你的需求。

不过要针对你的两个特殊任务做适配:

1. 控制台输入任务(Scanner)

把Scanner的输入逻辑包装成Callable:

import java.util.Scanner;
import java.util.concurrent.Callable;

public class ScannerInputTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("控制台输入:回车确认");
            return scanner.nextLine();
        } finally {
            scanner.close();
        }
    }
}

2. 按键输入任务(KeyListener)

KeyListener依赖AWT事件线程,需要用CountDownLatch来等待按键触发:

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import javax.swing.JFrame;
import javax.swing.JTextField;

public class KeyInputTask implements Callable<String> {
    private final CountDownLatch latch = new CountDownLatch(1);
    private String inputContent;

    @Override
    public String call() throws Exception {
        JFrame inputFrame = new JFrame("按键输入窗口");
        JTextField inputBox = new JTextField();
        inputFrame.add(inputBox);
        inputFrame.setSize(300, 100);
        inputFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        inputFrame.setVisible(true);

        inputBox.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    inputContent = inputBox.getText();
                    latch.countDown();
                    inputFrame.dispose();
                }
            }
        });

        // 阻塞等待按键触发
        latch.await();
        return inputContent;
    }
}

3. 主线程整合逻辑

用线程池执行任务,调用invokeAny()获取第一个结果:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new ScannerInputTask());
        taskList.add(new KeyInputTask());

        try {
            // 拿到第一个完成的结果,自动中断另一个任务
            String firstResult = threadPool.invokeAny(taskList);
            System.out.println("最先获取的输入:" + firstResult);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 强制关闭线程池,确保所有线程终止
            threadPool.shutdownNow();
        }
    }
}

为什么不用手动wait/notify?

你之前的写法有几个硬伤:

  • Thread.currentThread().wait()必须在同步块中调用,且锁对象必须是当前线程对象,这种用法本身就不符合多线程规范
  • 手动终止线程(比如你说的“delete thread”)没有安全的方式,只能通过中断信号,而invokeAny()已经帮你处理了中断逻辑
  • 手动处理线程完成的通知容易遗漏场景,比如线程抛出异常时无法触发notify

关键注意点

  • Scanner的nextLine()会响应中断,收到中断信号后直接抛出异常,任务终止
  • KeyInputTask中的latch.await()会捕获中断信号,抛出异常后任务终止,同时窗口会被关闭

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:55:18