Java线程调用join()方法阻塞问题:自定义邮件队列服务运行异常
阻塞问题产生原因
这是典型的锁持有等待导致的死锁问题,完整执行链路和死锁形成逻辑如下:
- 调用
sendNotificationEmail方法发送邮件结束后,工作线程完成邮件发送、触发lock.notify()唤醒主线程后,会回到while(run)循环,再次进入同步块调用lock.wait()释放锁并进入等待状态。 - 主线程从
sendNotificationEmail返回后调用close()方法,首先将run设为false,随后进入synchronized (lock)块拿到锁,调用lock.notify()唤醒工作线程,紧接着在仍持有lock锁的状态下调用thread.join()等待工作线程终止。 - 此时死锁条件正式成立:
- 主线程:持有
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
相关产品推荐
相关产品推荐

