Java 的 sleep 和 wait 有什么区别?分别会不会释放锁?
下面是一段教学用的模拟面试。
🧑💻 面试官:sleep 和 wait 都能让线程暂停,它们有什么区别?
🙋♂️ 我:sleep 暂停一段时间,wait 等其他线程通知。sleep 不释放锁,wait 会释放锁。
🧑💻 面试官:一个线程同时拿着 A 和 B 两把监视器锁,调用 A.wait(),B 也会释放吗?
🙋♂️ 我:不会,只释放 A 的监视器。
🧑💻 面试官:那另一个线程调用 A.notify(),等待线程能立刻往下执行吗?为什么等待条件通常要写在 while 里?
暂停只是表面现象。答清楚这道题,要追踪「暂停时释放哪把锁,回来时条件还成不成立」。
面试速答(60 秒版)
Thread.sleep 让当前线程暂停指定时间,适合暂缓执行。线程不会因为 sleep 释放已经持有的监视器锁,所以在同步块里睡觉,其他需要这把锁的线程仍然进不来。
Object.wait 则用来等待某个条件。调用前必须持有这个对象的监视器,等待时释放的是这个对象的监视器,不会把线程持有的其他锁一起释放。
被 notify、notifyAll 唤醒或等待超时以后,线程还需要重新获得监视器才能继续。并且,通知不代表条件一定满足,等待还可能发生虚假唤醒,因此通常在 while 中重新检查条件。两种调用都要正确处理中断,不能把“睡一会儿”当作可靠的线程协作方案。

知识点详解:一个线程等数据,另一个线程怎么把数据送进来?
sleep 只是暂停当前线程
假设一个后台任务暂时没有工作,代码希望过一秒再检查,可以让当前线程 sleep 一秒。
这里暂停的是调用 sleep 的线程。即使通过另一个 Thread 对象写出调用形式,也不能理解成“让那个对象代表的线程去睡”。sleep 是 Thread 的静态方法。
同时,暂停时间结束也不代表线程恰好在这一毫秒继续运行。它还需要获得实际执行机会。Thread API说明了计时和调度方面的限制。
更容易出问题的是把 sleep 放进同步块里。线程虽然暂时不干活,但锁还在它手上。其他线程如果要进入使用同一监视器的同步区域,仍然需要等待。
wait 为什么要先拿到锁,再把它释放?
假设咱们有一个队列。消费者要取数据,生产者要往里放数据,两边通过同一个监视器保护队列状态。
消费者拿到锁以后发现队列为空。这时如果抱着锁 sleep,生产者也要拿这把锁才能加入数据,两边就没法顺利配合。
消费者可以在检查条件后调用 wait:进入等待,同时释放这个对象的监视器。生产者于是能够拿到锁、加入数据,再发出通知。
因此,检查“有没有数据”和决定“开始等待”,需要在同一个受保护范围内配合。不能在锁外随便检查一下,再凭感觉调用 wait;没有持有对应监视器时,调用会抛出 IllegalMonitorStateException。
notify 发出以后,等待线程还要再过两关
第一关是锁。
生产者调用 notify 时,自己仍然持有监视器。notify 并不会让生产者立刻交出这把锁。等待线程即使被选中,也要等生产者退出相应同步范围,并重新竞争监视器,才有机会继续。
第二关是条件。
假设两个消费者都在等数据,生产者加入一条记录后调用 notifyAll。两个消费者都有机会醒来,但只能有一个先拿到这条记录。另一个重新拿到锁时,队列可能又空了。
所以,等待逻辑通常是这个意思:
获得队列的监视器
while 队列为空:
对同一监视器调用 wait
取出一条记录
退出同步范围
这里用的是控制流程说明,不是把某个 Python 或 TypeScript 库冒充 Java 监视器实现。
while 让线程回来后再次判断条件。除了多个消费者竞争,Java 也允许虚假唤醒,因此“等到了通知”不能代替“现在确实可以取数据”。Object API明确要求应用处理这类情况。

释放锁,要说清楚释放的是谁
假设线程先进入 A 的同步区域,再进入 B 的同步区域,最后调用 A.wait()。
它等待时释放 A 的监视器,但仍然持有 B 的监视器。如果生产者必须拿 B 才能生成数据,仍然可能被挡住。
因此,“wait 会释放锁”只是一句缩写,完整说法应该是:释放被调用对象的监视器,并在返回前重新获得它。不能把它解释成清空线程持有的所有锁,也不能认为 wait 会替你释放 ReentrantLock。
这也是为什么多把锁嵌套配合条件等待时,需要特别仔细地检查依赖关系。

怎么验证自己没有把协作写错?
用两个线程记录四个位置:开始等待、生产者拿到锁、发出通知、消费者返回 wait。
让生产者发出通知后继续持锁一小段时间,可以观察消费者不会立即从 wait 返回。再让两个消费者同时等一条数据,检查没有数据的消费者是否继续等待。
测试不要靠“sleep 两秒应该差不多了”判定顺序。用明确的同步信号控制实验阶段,再检查结果;否则偶然通过只能说明这次调度碰巧合适。
面试官继续追问
notify 和 notifyAll 怎么选?
notify 选择一个等待该对象的线程,并不按你期望的业务条件挑选。多个条件共用一个监视器时,选错等待者可能影响进展。notifyAll 让等待者重新竞争并检查条件,但也可能带来额外唤醒。复杂场景可以用更明确的 Condition 或现成并发队列。
wait 超时了,就一定能马上返回吗?
不保证。等待超时以后,仍然需要重新获得监视器,并等待调度。超时预算不能只按参数数值理解。
InterruptedException 可以直接忽略吗?
不应当随手吞掉。中断常用于取消和停止任务,需要按调用约定传播,或恢复中断状态并结束相应工作。具体处理取决于任务职责,不是每次异常后都继续无限循环。
面试速记卡
- sleep:暂停当前线程,不释放已持有的监视器锁。
- wait:先持有对象监视器,等待时只释放这个监视器。
- notify:发出通知,不立即释放通知者持有的锁。
- 返回:先重新拿到锁,再检查条件,通常使用 while。
- 协作:等待条件、中断和锁依赖都要明确,不靠睡眠猜顺序。
公司面试真题
真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。
美团 · Java后端 · 实习
wait 和 sleep 有什么区别?(题意整理)
4.21美团Java实习一二面面经 ↗
面试记录为 2020-04-21、2020-04-24;原帖编辑于 2020-11-14