sleep()

sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行机会,也可以让低优先级的线程得到执行机会。但是 sleep() 方法不会释放”锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。

wait()

wait() 方法需要和 notify() 及 notifyAll() 两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在 synchronized 语句块内使用,也就是说,调用 wait(),notify() 和 notifyAll() 的任务在调用这些方法前必须拥有对象的锁。注意,它们都是 Object 类的方法,而不是 Thread 类的方法。

wait() 方法与 sleep() 方法的不同之处在于,wait() 方法会释放对象的”锁标志”。当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。

除了使用 notify() 和 notifyAll() 方法,还可以使用带毫秒参数的 wait(long timeout) 方法,效果是在延迟 timeout 毫秒后,被暂停的线程将被恢复到锁标志等待池。

此外,wait(),notify() 及 notifyAll() 只能在 synchronized 语句中使用,但是如果使用的是 ReenTrantLock 实现同步,该如何达到这三个方法的效果呢?解决方法是使用 ReenTrantLock.newCondition() 获取一个 Condition 类对象,然后 Condition 的 await(),signal() 以及 signalAll() 分别对应上面的三个方法。

yield()

yield() 方法和 sleep() 方法类似,也不会释放”锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执行,另外 yield() 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep() 方法不同。

join()

join() 方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行

四个方法的单点描述齐全,但区别题的高分答案是维度化对照:谁在等、等多久、释放什么锁、能否被中断、归属哪个类。本文先给五维对照表,再逐个补机制细节与典型误用。

五维对照表

维度 sleep(t) wait() yield() join()
归属类 Thread Object Thread Thread
释放锁 不释放 释放 不释放 不释放(但等待期间释放目标线程对象的锁——见下)
等待终点 时间到 notify/notifyAll/超时/中断 无等待,仅让出 CPU 一次 目标线程结束(或超时)
状态 TIMED_WAITING WAITING/TIMED_WAITING RUNNABLE→RUNNABLE WAITING/TIMED_WAITING
可中断 是 是 否 是
使用位置 任意 synchronized 内 任意 任意

机制细节补全

sleep:暂停当前线程,不让出已持有的锁——“抱着锁睡觉”,故 sleep 在同步块内会阻塞其他竞争者;用途是定时暂停/轮询间隔(更优替代:条件变量或 Scheduled 线程池)。

wait/notify:Object 方法的原因——锁是对象级的,等待队列挂在对象头/monitor 上,与哪个线程调用无关。wait 的完整动作:释放锁→入等待队列→被唤醒后重新竞争锁→拿到锁才从 wait 返回。因此 wait 必须在循环中检查条件(防虚假唤醒与竞争后条件已变):

synchronized (lock) {
while (!condition) lock.wait(); // 循环+条件,标准范式
doWork();
}

yield:静态方法,提示调度器”我可让出”,但调度器可无视(现代 JVM 上几乎无实际效果);不阻塞、不等待、不释放锁。生产代码不应依赖 yield 做同步或限流——它只是 CAS 自旋锁内部的优化手段(让出 CPU 给持锁线程)。

join:t.join() 的语义是”当前线程等 t 结束”。实现是 synchronized(t) + while(t.isAlive()) t.wait()——所以 join 等待期间释放的是 t 对象的锁而非当前线程持有的其他锁;线程结束时 JVM 自动 notifyAll 该线程对象上的等待者。join(t) 带超时防永久等待。

高频误用清单

  • 用 sleep 做线程协作(”sleep 一会儿对方应该写完了”)→ 竞态窗口,应 wait/notify 或 CountDownLatch/join
  • 同步块内 sleep 长时 → 持锁睡觉阻塞全局,应把 sleep 移出锁或改 wait 释放锁
  • wait 不写循环条件 → 虚假唤醒/条件竞态 bug
  • 用 yield 试图”保证 fairness” → 无保证,公平性靠锁策略(公平锁/排队)
  • join 放在 start 之前/目标线程未启动 → join 一个未 start 的线程立即返回(isAlive=false),等待失效

与 Lock/Condition 的对应

ReentrantLock 体系:condition.await()/signal()/signalAll() 对应 wait/notify/notifyAll,且一个 Lock 可建多个 Condition(notFull/notEmpty 分组唤醒,避免 notifyAll 全量唤醒);sleep/yield/join 无对应物(它们不涉及条件等待)。

记忆锚点

一句话串联:sleep 抱锁打盹、wait 放锁排队、yield 让位不歇、join 等人死亡(线程结束);归属上 wait 是 Object 的(锁在对象上),其余是 Thread 的;可中断性上只有 yield 免疫中断。五维表+四句口诀,区别题与误用排查都够用。