Java Object.wait(long timeout) 存在一個長期存在的語義模糊點:方法返回時,無法區分是被 notify() 喚醒、還是超時自動返回、或是虛假喚醒。這導致需要精確超時控制的場景(如連接池獲取、分佈式鎖、事務協調)難以正確實現邏輯分支。本文剖析問題根源,提供一種通用、無侵入的解決方案模式。
JLS/JVMS 規定 wait(timeout) 可能因三種原因返回:
notify()/notifyAll()。標準 API 無任何返回值、異常、標誌位區分上述情況。開發者若需「超時執行降級邏輯、通知執行正常邏輯」,無法直接實現。
// 錯誤:無法區分超時與通知
synchronized(lock) {
lock.wait(5000);
if (/* 怎麼判斷? */) { handleTimeout(); } else { handleNotify(); }
}
常見錯誤補救:
notified = true,待方檢查。但需額外同步、易漏設置、擴展性差。核心思想:將「等待動作」與「結果判斷」解耦,引入顯式的等待結果枚舉。
public enum WaitResult {
NOTIFIED, // 收到通知
TIMED_OUT, // 超時返回
INTERRUPTED // 被中斷
}
public final class WaitUtils {
private WaitUtils() {}
/**
* 在持有鎖的前提下,等待條件滿足或超時/中斷。
* @param lock 同步鎖(調用者必須持有)
* @param condition 條件判斷 Supplier,返回 true 表示條件滿足
* @param timeoutMs 超時毫秒數,<=0 表示無限等待
* @return 等待結果枚舉
*/
public static WaitResult waitFor(Object lock, BooleanSupplier condition, long timeoutMs)
throws InterruptedException {
long deadline = timeoutMs > 0 ? System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs) : 0;
while (true) {
if (condition.getAsBoolean()) {
return WaitResult.NOTIFIED; // 條件已滿足(可能被通知、也可能進入時就滿足)
}
if (Thread.interrupted()) {
throw new InterruptedException("等待被中斷");
}
long remainingMs = timeoutMs > 0 ? TimeUnit.NANOSECONDS.toMillis(deadline - System.nanoTime()) : 0;
if (timeoutMs > 0 && remainingMs <= 0) {
return WaitResult.TIMED_OUT;
}
try {
lock.wait(remainingMs > 0 ? remainingMs : 0);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中斷標誌
throw e;
}
}
}
}
class ConnectionPool {
private final Object lock = new Object();
private final Queue free = new ArrayDeque<>();
Connection borrow(long timeoutMs) throws InterruptedException, TimeoutException {
WaitResult result = WaitUtils.waitFor(lock, () -> !free.isEmpty(), timeoutMs);
synchronized(lock) { // 重新獲取鎖以安全取出連接
if (result == WaitResult.NOTIFIED && !free.isEmpty()) {
return free.poll();
}
if (result == WaitResult.TIMED_OUT) {
throw new TimeoutException("獲取連接超時: " + timeoutMs + "ms");
}
throw new InterruptedException("獲取連接被中斷");
}
}
void release(Connection conn) {
synchronized(lock) {
free.offer(conn);
lock.notifyAll();
}
}
}
java.util.concurrent.locks.Condition 原生支持帶返回值的 await(timeout):
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
boolean awaitResult = notEmpty.await(timeoutMs, TimeUnit.MILLISECONDS);
// 返回 true:被 signal/signalAll 喚醒
// 返回 false:超時返回
// 拋 InterruptedException:被中斷
強烈建議新項目直接使用 Lock + Condition,完全替代 synchronized/wait/notify,語義清晰、功能豐富(多條件隊列、公平鎖、可中斷鎖等)。
WaitUtils.waitFor() 模式封裝 Object.wait(),統一返回 WaitResult,消除語義模糊。ReentrantLock + Condition,原生支持超時/中斷/通知三態區分。徹底解決 wait() 超時語義模糊,是構建可靠併發組件(連接池、限流器、分佈式協調器)的關鍵一步。