
本文深入探讨了在多线程环境中,如何使用Java的`wait()`和`notify()`机制来协调消息发送者线程与会话重连守护线程的工作。通过分析一个实际的SMS消息发送场景中的同步问题,文章详细阐述了竞态条件、不恰当的同步对象使用以及`wait`/`notify`误用导致的问题,并提供了一套基于专用锁对象和正确同步逻辑的优化解决方案,旨在帮助开发者理解并正确应用这些并发原语。
引言:多线程消息发送与会话管理挑战
在企业级应用中,批量发送消息(如SMS)是一个常见需求。为了提高吞吐量,通常会采用多线程并发发送的方式。然而,这种模式引入了复杂的并发控制问题,尤其是在涉及共享资源和状态管理时。一个典型的场景是,多个发送线程共享一个会话对象(例如SMPPSession),该会话可能因为网络问题而断开。此时,需要一个独立的“守护”线程来负责检测会话状态并进行重连。在会话重连期间,所有的发送线程必须暂停,直到会话恢复正常才能继续。
Java提供了Object类的wait()、notify()和notifyAll()方法,配合synchronized关键字,可以实现线程间的协作与通信。然而,不恰当的使用这些机制极易导致死锁、竞态条件或IllegalMonitorStateException等问题。本文将通过一个具体的SMS发送示例,深入分析常见的同步陷阱,并提供一套健壮的解决方案。
wait和notify机制深入解析
wait()、notify()和notifyAll()是Java中用于线程间协作的低级并发原语,它们都属于Object类。
- synchronized块: 使用wait()和notify()系列方法的前提是当前线程必须持有目标对象的监视器锁(monitor lock)。这意味着这些方法必须在synchronized块内部调用,否则会抛出IllegalMonitorStateException。
- wait(): 当一个线程调用某个对象的wait()方法时,它会立即释放该对象的锁,并进入等待状态,直到被其他线程唤醒(通过notify()或notifyAll())或被中断。
- notify(): 唤醒在该对象上等待的一个任意线程。
- notifyAll(): 唤醒在该对象上等待的所有线程。
关键点:
- 释放锁: wait()方法会释放锁,而notify()/notifyAll()不会立即释放锁,它们只是将等待队列中的线程移动到就绪队列,直到当前线程退出synchronized块或再次调用wait(),锁才会被释放。
- while循环检查: 调用wait()方法时,必须将其放在一个while循环中,以防止“虚假唤醒”(spurious wakeup)和条件未满足时被唤醒的情况。线程被唤醒后,应重新检查等待条件是否满足。
原代码中的同步问题分析
原始代码尝试使用Client.messages列表作为同步对象来协调Sender和SessionProducer线程,但存在以下几个核心问题:
1. 竞态条件导致ArrayIndexOutOfBoundsException
在Sender线程的run()方法中,存在如下逻辑:
while (!Client.messages.isEmpty()){ // (1) 外部循环条件检查
synchronized (Client.messages){ // (2) 进入同步块
if (smppSession.isBind()){
final String msg = Client.messages.remove(0); // (3) 移除消息
// ...
} else {
try {
Client.messages.wait();
} catch (InterruptedException e) { /* ... */ }
}
}
}问题在于(1)处的!Client.messages.isEmpty()检查是在没有持有Client.messages锁的情况下进行的。当多个Sender线程并发执行时,可能会出现以下场景:
- 假设Client.messages中只有一条消息。
- 所有`Sender










