
wait() 仅在另一线程已进入等待状态后调用 notify() 才有效;若 notify() 先于 wait() 执行(即“通知丢失”),线程将永久阻塞——这是由 monitor 机制的无状态性决定的,非代码逻辑错误。
`wait()` 仅在另一线程**已进入等待状态后**调用 `notify()` 才有效;若 `notify()` 先于 `wait()` 执行(即“通知丢失”),线程将永久阻塞——这是由 monitor 机制的无状态性决定的,非代码逻辑错误。
在 Android 蓝牙管理等异步场景中,开发者常误用 Object.wait()/notify() 实现“等待蓝牙开启”,却遭遇线程卡死问题。根本原因不在于语法错误,而在于对 Java 监视器(monitor)唤醒机制的深层误解。
? 为什么你的 lock.wait() 永远不返回?
你代码中的关键时序缺陷如下:
// 线程A(主线程/调用 enableBluetooth)
synchronized (lock) {
mBluetoothAdapter.enable(); // 异步开启,立即返回
lock.wait(); // ✅ 当前线程进入 WAITING 状态,释放 lock
}
// 线程B(Handler 回调线程)
if (bluetoothAdapter.isEnabled()) {
synchronized (lock) {
lock.notify(); // ❌ 若此时线程A尚未执行到 wait()(或已超前执行完),
// 则 notify 无效——monitor 不保存“未消费的通知”
}
}
wait()/notify() 是无状态信号机制:notify() 不会“记住”自己被调用过,它只唤醒此刻正在该对象等待队列(WaitSet)中的线程。若 notify() 发生在 wait() 之前(常见于异步回调、Handler 延迟调度等场景),该通知即彻底丢失,wait() 将无限期阻塞。
此外,你的 Runnable 中存在严重并发隐患:
-
phandler.postDelayed(this, 10000)在蓝牙未开启时反复注册,但removeCallbacks(this)仅在isEnabled()为 true 时执行,极易导致重复回调; -
enable()是异步操作,isEnabled()立即返回false,造成postDelayed循环,而notify()却只在true分支触发——这进一步放大了竞态窗口。
✅ 正确解法:用有状态同步工具替代无状态 notify
方案一:Semaphore(推荐|简洁可靠)
Semaphore 内置计数器,可“暂存”通知,完美规避丢失问题:
private final Semaphore bluetoothReady = new Semaphore(0); // 初始无许可
public void enableBluetooth() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null && !adapter.isEnabled()) {
if (checkPermission()) {
adapter.enable(); // 异步
try {
bluetoothReady.acquire(); // 阻塞直到 acquire 到许可
isBluetoothOn = true;
onBluetoothConnected();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else if (adapter != null) {
isBluetoothOn = true;
onBluetoothConnected();
}
}
// 在 Runnable 中(确保与 acquire 同一锁上下文非必需,Semaphore 是线程安全的)
if (adapter.isEnabled()) {
Log.i(TAG, "Bluetooth is ON → releasing semaphore");
bluetoothReady.release(); // ✅ 即使 acquire 尚未执行,许可也会被暂存
handler.removeCallbacks(this);
}
方案二:CountDownLatch(单次事件)
适用于“等待一次开启完成”场景:
private CountDownLatch latch;
public void enableBluetooth() {
latch = new CountDownLatch(1);
// ... adapter.enable()
try {
latch.await(); // 等待倒数归零
} catch (InterruptedException e) { /* handle */ }
}
// 在 Runnable 中
if (adapter.isEnabled()) {
latch.countDown(); // ✅ 安全:多次调用无副作用
}
方案三:Condition(需配合 ReentrantLock,更灵活)
若需复位、多条件等待(如“等待开启”+“等待关闭”),Condition 提供精确控制:
private final ReentrantLock lock = new ReentrantLock();
private final Condition bluetoothOn = lock.newCondition();
// await
lock.lock();
try {
while (!isBluetoothOn()) {
bluetoothOn.await();
}
} finally {
lock.unlock();
}
// signal(在适当时机,如 BroadcastReceiver 收到 ACTION_STATE_CHANGED)
lock.lock();
try {
isBluetoothOn = true;
bluetoothOn.signal(); // ✅ 精准唤醒,且支持 signalAll
} finally {
lock.unlock();
}
⚠️ 关键注意事项
-
永远不用
wait()/notify()做跨异步任务通信:它们设计初衷是同一锁保护下的协作式线程调度(如生产者-消费者),而非异步事件通知。 -
必须用
while循环检查条件:防止虚假唤醒(spurious wakeup)导致逻辑错误。 -
notify()≠notifyAll():前者随机唤醒一个,后者唤醒全部;但在你的场景中,二者都解决不了通知丢失问题。 -
Android 权限与生命周期:
BLUETOOTH_CONNECT是运行时权限,且enable()在 Android 12+ 已被弃用,应改用BluetoothAdapter.enable()的替代方案(如ActivityResultLauncher请求用户开启)并监听ACTION_STATE_CHANGED广播。
✨ 总结
wait()/notify() 的失效不是 Bug,而是其底层 monitor 语义的必然结果——它不提供“事件暂存”能力。在现代 Java 并发编程中,应优先选用 java.util.concurrent 包中的有状态同步工具:
✅ Semaphore(带计数的信号量)→ 解决通知丢失
✅ CountDownLatch(一次性门闩)→ 简化单次等待
✅ Condition(条件队列)→ 支持复杂条件与重入
摒弃对 Object 监视器的过时依赖,才能写出健壮、可维护的异步协调逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











