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

Java线程调用join()方法阻塞问题:自定义邮件队列服务运行异常

阻塞问题产生原因

这是典型的锁持有等待导致的死锁问题,完整执行链路和死锁形成逻辑如下:

  1. 调用sendNotificationEmail方法发送邮件结束后,工作线程完成邮件发送、触发lock.notify()唤醒主线程后,会回到while(run)循环,再次进入同步块调用lock.wait()释放锁并进入等待状态。
  2. 主线程从sendNotificationEmail返回后调用close()方法,首先将run设为false,随后进入synchronized (lock)块拿到锁,调用lock.notify()唤醒工作线程,紧接着在仍持有lock锁的状态下调用thread.join()等待工作线程终止。
  3. 此时死锁条件正式成立:
    • 主线程:持有lock锁,阻塞在join()方法,只有等工作线程终止才会释放锁
    • 工作线程:被notify()唤醒后需要先获得lock锁才能继续执行后续逻辑、判断run状态退出循环终止,但锁被主线程持有永远拿不到,因此永远不会终止
      最终主线程就会永久卡在join()方法处。
解决方案

核心修复:将join()操作移出锁同步块

只需调整close方法的逻辑,在唤醒工作线程后立即释放锁,再等待工作线程终止即可,修改后的代码如下:

public void close() throws InterruptedException {
    run = false;
    // 同步块仅负责唤醒工作线程,执行完成后立即释放lock锁
    synchronized (lock) {
        lock.notify();
        System.out.println("Thread will join " + thread.isInterrupted());
    }
    // 释放锁后再执行join,工作线程可以拿到锁完成后续执行
    thread.join();
    System.out.println("Thread after join");
}

其他可选优化(规避潜在异常)

  • 修复队列操作的线程安全问题:当前sendNotificationEmail方法中emailQueue.add(email)是在同步块外执行的,多线程调用时会出现并发异常,需要将队列操作移入同步块:
    public void sendNotificationEmail(Email email) throws InterruptedException {
        synchronized (lock) {
            emailQueue.add(email);
            lock.notify();
            lock.wait();
        }
    }
    
  • 处理wait的假唤醒问题:所有lock.wait()调用都要放在条件循环中,不要单次调用。比如工作线程中的wait可以修改为:
    synchronized (lock) {
        try {
            // 只有队列空且服务仍在运行时才等待,规避假唤醒导致的逻辑异常
            while (emailQueue.getSize() == 0 && run) {
                lock.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 后续处理逻辑
    }
    
  • 若业务要求关闭服务时必须把队列中所有剩余邮件都发送完成,可以修改工作线程的循环判断条件为while (run || !emailQueue.isEmpty()),避免未处理的邮件丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 20:45:03