Java Servlet(Jetty)中Object的wait/notify无法唤醒线程问题
遇到这种线程一直醒不来的情况,大概率是wait/notify的使用姿势不对,结合Servlet的运行环境,我给你列几个最常见的原因,你可以逐一排查:
锁对象不是同一个实例
如果你在doPost方法里把lock声明成了局部变量,那发起异步请求的回调线程拿到的lock,和当前调用wait的线程持有的lock根本不是同一个对象!notify只能唤醒等待在同一个锁对象上的线程,这种情况下自然唤不醒。解决办法是让锁对象能被回调线程访问到——比如每个请求创建一个独立的lock(用final修饰,让匿名内部类能捕获),或者如果是全局回调的话,要确保锁对象是全局可见的(但不推荐全局锁,会导致请求间互相干扰)。wait/notify没有在synchronized块内调用
这是wait/notify的核心规则:调用wait()前必须先持有该锁对象的监视器(也就是进入synchronized(lock)块),调用notify()同理。如果你的代码里没加这个同步块,要么会直接抛出IllegalMonitorStateException,要么线程根本没进入合法的等待队列,后续的notify也找不到它。一定要检查两边的代码:当前线程的wait、回调线程的notify,都要包裹在同一个lock的synchronized块里。notify调用时机过早
要是回调线程在当前线程调用wait()之前就执行了notify(),那当前线程再进入wait状态后,就没人来唤醒它了。这种情况很容易发生在异步请求响应太快的场景里。解决办法是加一个状态标志,比如用AtomicBoolean记录“是否收到响应”,当前线程在wait前先检查这个标志,如果还没收到再wait;回调线程收到响应后先更新标志,再调用notify。而且要用while循环检查状态,而不是if——因为Java里存在“虚假唤醒”的情况,线程可能会无缘无故被唤醒,这时候必须再次确认状态是否满足。Servlet单例导致的锁冲突
默认情况下Servlet是单例的,如果你的lock是Servlet的成员变量,那多个请求会共用同一个锁。比如请求A先进入wait,这时候请求B的回调调用了notify,唤醒的可能是请求B的线程(如果它也在wait),而请求A就一直等着。这种场景下,一定要给每个请求分配独立的锁对象,比如在doPost里创建局部的lock,确保每个请求的wait/notify互不干扰。忽略了InterruptedException
如果wait过程中线程被中断(比如Jetty超时关闭请求线程),会抛出InterruptedException,要是你没捕获这个异常,线程可能直接退出,不会继续等待。所以在wait周围一定要加try-catch,并且记得恢复线程的中断状态(比如Thread.currentThread().interrupt()),避免后续逻辑出错。
给你贴一个符合规范的示例代码,你可以对比下自己的写法:
private final AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 每个请求独立的锁和状态标记 final Object lock = new Object(); final AtomicBoolean isResponseReady = new AtomicBoolean(false); final StringBuffer responseContent = new StringBuffer(); // 发起异步请求到第三方服务 asyncHttpClient.preparePost("http://your-other-service/endpoint") .setBody(req.getInputStream()) .execute(new AsyncCompletionHandler<Response>() { @Override public Response onCompleted(Response response) throws Exception { synchronized (lock) { responseContent.append(response.getResponseBody()); isResponseReady.set(true); lock.notify(); // 必须在同步块内调用notify } return response; } @Override public void onThrowable(Throwable t) { synchronized (lock) { responseContent.append("Request failed: ").append(t.getMessage()); isResponseReady.set(true); lock.notify(); } } }); // 等待第三方服务响应 synchronized (lock) { while (!isResponseReady.get()) { // 用while循环处理虚假唤醒 try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ServletException("Wait interrupted while waiting for response", e); } } } // 向客户端返回结果 resp.setContentType("application/json"); resp.getWriter().write(responseContent.toString()); }
这个示例里的关键点:每个请求用独立的锁和状态,wait用while循环检查,notify在同步块内,同时处理了异常情况。
内容的提问来源于stack exchange,提问作者Dominic Mortlock

