前阵子帮朋友模拟面试,被问到“wait和sleep的区别”,他张口就来:wait会释放锁,sleep不会。我再追问一句:那为什么wait必须放在synchronized里,sleep不用?他愣了一下,然后开始背八股。这个场景我见过太多次了,绝大多数人能把区别背出来,但真到让他解释“为什么这么设计”,或者写一段代码验证一下,就露馅了。
所以这篇就把wait()和sleep()彻底讲透,从源码出身、锁行为、设计意图到面试话术、实战选型、踩坑记录,一次性聊完。不管你是准备校招面试、社招跳槽,还是工作中被多线程问题折磨过的同学,这篇都值得你花十分钟静下心看完。
1. 先看表象:wait()和sleep()的一页纸差异
聊区别之前,先立一个总纲。很多面试题看起来复杂,其实就是把表象和本质混在一起问,你能分清层级,答案自然有深度。
1.1 差异速查表
我先把最核心的差异列成一张表,这也是面试时最好用的“骨架记忆”:
| 对比维度 | wait() | sleep() |
|---|---|---|
| 所属类 | Object类 | Thread类 |
| 方法性质 | 实例方法 | 静态方法 |
| 调用前提 | 必须在synchronized同步块/方法中调用 | 任意位置都可调用 |
| 是否释放锁 | 会释放当前持有的监视器锁 | 不会释放任何锁 |
| 唤醒方式 | 需要notify()/notifyAll()唤醒,或等待超时 | 时间一到自动唤醒 |
| 用途定位 | 线程间通信与协作 | 线程暂停执行、模拟耗时 |
| 对CPU影响 | 不占用CPU,进入WAITING/TIMED_WAITING状态 | 不占用CPU,进入TIMED_WAITING状态 |
这张表是基础,但只有这张表是不够的。面试官只要多问一句“为什么”,马上就能区分你是背出来的还是真懂的。
1.2 最容易忽略的一个隐藏差异
除了上面这些,还有一个特别容易被忽视的差别:wait(0)和sleep(0)的含义完全不同。
- wait(0):表示永久等待,直到被notify唤醒,等价于wait()。
- sleep(0):表示不睡眠,但会触发一次线程调度器对同优先级线程的重新竞争,相当于主动让出一次CPU执行机会。
这个细节在面试中出现频率不高,但在排查一些偶发性的性能问题时非常有用。我自己就有过一段经历——线上某个定时任务偶发性延迟,排查半天发现是代码里用sleep(0)想“礼让”CPU,结果在特定负载下反而引入了调度抖动。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么wait()属于Object,sleep()属于Thread?从设计源头找答案
很多人死记“wait在Object,sleep在Thread”,但从来没想过为什么。这两个归属本身就是理解它们差异的钥匙。
2.1 管程模型与wait()的“出身”
Java的并发模型基于管程(Monitor)思想。管程的核心是:同一时刻只允许一个线程进入临界区,线程之间通过条件变量进行等待和通知。
在Java里,每个对象都内置了一把锁(Monitor Lock),这把锁不是Thread私有的,而是和对象绑定的。wait()的语义是“当前线程在某个对象上等待”,既然是“在对象上等待”,那它天然就应该属于Object——因为等待这个动作是作用于对象的锁和条件队列上的,而不是作用于某个线程的。
你调用obj.wait(),本质上是说:把当前线程放进obj的这个条件等待队列里,同时释放掉当前线程持有的obj的监视器锁。所以wait()和对象绑定,和锁绑定,把它放在Object里是逻辑必然。
这也是为什么wait()必须在synchronized块内调用。你没持有对象锁,就没有资格操作这个对象的等待队列。就像你没有会议室钥匙,就不能把别人锁在会议室外面一样。
2.2 sleep()的“出身”与它的职责定位
而sleep()是Thread的静态方法,它能直接通过Thread.sleep()调用,和任何对象锁都没有关系。它的语义是“当前线程暂停执行一段时间”,仅仅影响当前线程自身的执行节奏。
你可以粗暴地理解为:sleep是“线程自己休息”,wait是“线程之间互相配合”。一个针对自身行为,一个针对协作关系,设计归属自然不同。
从设计意图上看,Object负责的是线程间的协调机制(配合synchroniz
