Java 线程休眠、等待、加入、让步的区别
在 Java 中,线程的 休眠(sleep)、等待(wait)、加入(join) 和 让步(yield) 是四种不同的线程协作或控制方式,它们的作用、机制和使用场景各不相同。
在线程的生命周期中,不同状态之间切换时,可以通过调用sleep()、wait()、join()、yield()等方法进行线程状态控制。

以下是它们的详细区别:
| 方法 | 是否释放锁 | 是否阻塞 | 是否需要同步上下文 | 是否可中断 | 用法 | 典型用途 |
|---|---|---|---|---|---|---|
| wait() | 是 | 是 | 是 | 是 | Object.wait | 线程间协作(生产者-消费者) |
| sleep() | 否 | 是 | 否 | 是 | Thread.sleep | 定时暂停 |
| join() | 否 | 是 | 否 | 是 | Thread.join | 等待线程结束。 |
| yield() | 否 | 否 | 否 | 否 | Thread.yield | 礼让 CPU(提示) |
线程的5种状态:
新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread(),且threadStatus = 0。
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start(),就绪状态的线程,随时可能被CPU调度执行。
运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
阻塞状态(Blocked) : 线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。在等待进入一个临界区,阻塞的情况分三种:
- 等待阻塞(等待唤醒)(in Object.wait()) – 通过调用线程的**wait()**方法,让线程等待某工作的完成。位于对象等待池中的阻塞状态(Blocked in object’s wait pool),涉及线程通信。如果大量线程在该状态,获得了监视器之后,又调用了wait() 方法
- 同步阻塞(等待获取监视器)(waiting for monitor entry) – 线程获取同步锁失败 (因为锁被其它线程所占用),它会进入同步阻塞状态。位于对象锁池中的阻塞状态(Blocked in object’s lock pool),涉及线程同步。如果大量线程在该状态,可能是争夺一个全局锁而被阻塞(某线程在临界区时间太长,以至于新线程迟迟无法进入临界区)
- 其他阻塞(等待资源)(waiting on condition) – 通过调用线程的**sleep()或join()**或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。如果大量线程在该状态,可能获取第三方资源网络阻塞,迟迟得不到响应,导致大量线程进入等待状态。或者IO读写较慢。
消亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
休眠(sleep)
作用:让当前线程暂停执行指定的时间,但不会释放锁(不释放已持有的对象锁)。
通过设置方法中的时间参数,使调用它的线程休眠指定时间,线程从Running(运行)状态转为Blocked(阻塞)状态。时间结束后自动恢复运行(进入就绪状态 Runnable )。
这个过程中会释放CPU资源,给其他线程运行机会时不考虑线程的优先级,但如果有同步锁则不会释放锁,其他线程无法获得同步锁。
stateDiagram-v2
[*] --> Runnable
Runnable --> Sleeping: 调用 Thread.sleep(millis)
Sleeping --> Runnable: 休眠时间结束 或 被中断 (InterruptedException)
Sleeping --> [*]: 线程终止(可选路径)
休眠时间未到时,可通过调用interrupt()方法来唤醒休眠线程。
1 | try { |
sleep是线程级别的休眠,不涉及到对象类,只是让当前线程暂停,进入休眠状态,并不释放同步锁资源,也不需要去获得对象锁。
不释放已持有的对象锁(即使在 synchronized 块中)。
会抛出
InterruptedException,必须处理。时间结束后自动恢复运行(进入就绪状态)。
等待(wait)
作用:让当前线程在某个对象上等待,会释放该对象的锁,直到被其他线程唤醒(notify/notifyAll)。
它是Object类的成员本地方法,会让持有对象锁的线程释放锁,并进入线程等待池中等待被唤醒,即在池中竞争同步锁,同时释放CPU资源。需要配合其他方法使用:
notify(随机或顺序唤醒),notifyAll全部唤醒,线程结束自动唤醒
- notify 的唤醒顺序取决于JVM的实现,可能是随机,可能是顺序唤醒
- 在hotspot 虚拟机中,是顺序唤醒,每次取出第一个等待的元素
stateDiagram-v2
[*] --> Runnable
Runnable --> Blocked: 尝试进入 synchronized 块(获取锁)
Blocked --> Runnable: 成功获取对象监视器锁
Runnable --> Waiting: 调用 obj.wait()
Waiting --> Runnable: 其他线程调用 obj.notify() 或 obj.notifyAll()
Waiting --> Runnable: 被中断(抛出 InterruptedException)
Waiting --> [*]: 线程结束(可选)
它的调用必须在同步方法或同步代码块中执行,也需要捕获 InterruptedException 异常。
1 | //同步代码块 |
每个对象都拥有各自的对象锁,wait的作用是释放当前线程占有的对象锁,自然是要操作对应的Object而不是Thread
必须在同步上下文中调用(否则抛出
IllegalMonitorStateException)。会释放对象锁,允许其他线程进入同步块。
可设置超时时间(
wait(long timeout)),超时后自动唤醒。通常与
notify()/notifyAll()配合使用,实现线程间通信。
加入(join)
作用:让当前线程等待另一个线程执行完毕后再继续执行。
调用
join()的线程拥有优先使用CPU时间片的权利,其他线程需要等待join()调用线程执行结束后才能继续执行
stateDiagram-v2
[*] --> MainRunning
[*] --> ChildRunning
state "主线程" as MainRunning
state "子线程" as ChildRunning
state "主线程阻塞等待" as MainWaiting
state "子线程终止" as ChildTerminated
MainRunning --> MainWaiting: 调用 childThread.join()
ChildRunning --> ChildTerminated: 子线程执行完毕
MainWaiting --> MainRunning: 子线程终止(join 返回)
ChildTerminated --> [*]
MainRunning --> [*]
note right of MainWaiting
主线程进入 WAITING 或 TIMED_WAITING 状态,
等待子线程结束。
end note
note left of ChildRunning
子线程独立运行,
不受 join 影响。
end note
1 | //创建TestRunnable类 |
底层主要通过wait()实现,参数代表等待当前线程最多执行多少毫秒,如果 为 0,则会一直执行,直至完成,才会轮到其他线程继续;
1 | public final synchronized void join(long millis) throws InterruptedException { |
调用
t.join()的线程会阻塞,直到t终止。内部使用了
wait()机制(但对用户透明)。也会抛出
InterruptedException。
让步(yield)
作用:提示调度器Thread.yield() 让当前线程主动让出 CPU 资源,给其他相同优先级或更高优先级的线程运行的机会
提出申请释放CPU资源,在多线程编程中可以用来影响线程执行顺序和资源分配,至于能否成功释放由JVM决定
避免线程长时间占用 CPU
- 在某些情况下,一个线程可能会长时间占用 CPU 资源,导致其他线程无法得到执行的机会。这时,可以在适当的时候调用
yield方法,让当前线程暂停一下,给其他线程一个执行的机会。例如,在一个复杂的计算任务中,如果一个线程一直在进行计算而不释放 CPU 资源,可能会导致其他线程无法及时响应用户的操作。通过在计算过程中定期调用yield方法,可以让其他线程有机会执行,提高系统的响应性。平衡线程的执行时间
- 在多线程环境中,不同的线程可能具有不同的执行时间和优先级。如果某些线程的执行时间过长,可能会导致其他线程等待时间过长,影响系统的整体性能。通过在长执行时间的线程中适当调用
yield方法,可以让其他线程有机会执行,从而平衡各个线程的执行时间,提高系统的整体效率。提高线程的公平性
- 在某些情况下,线程的调度可能会出现不公平的情况,导致某些线程长时间无法得到执行的机会。通过在适当的时候调用
yield方法,可以让当前线程主动让出 CPU 资源,给其他线程一个执行的机会,从而提高线程的公平性。
原理
给相同优先级或更高优先级的线程运行的机会执行权(也可能是自己本身),自己会处于就绪状态。
但是线程优先级高的也不一定的获得执行权, 优先级高仅仅只是执行概率大了一点。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。
stateDiagram-v2
[*] --> Runnable
Runnable --> Yielding: 调用 Thread.yield()
Yielding --> Runnable: 线程调度器重新调度(可能立即回到运行)
note right of Yielding
yield() 建议当前线程让出 CPU,
但不保证其他线程一定获得执行权。
线程仍处于 RUNNABLE 状态(Java 中无独立“Yielding”状态)。
end note
sleep()方法调用后线程处于阻塞TIME_WAITING状态,所以yield()方法调用后线程只是暂时的将调度权让给别人,但立刻可以回到竞争线程锁的状态;
Yield 是一种启发式尝试,旨在改善线程之间的相对进展,否则会过度使用 CPU。 它的使用应与详细的分析和基准测试相结合,以确保它确实具有预期的效果。很少适合使用这种方法。 它对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件引起的错误。 在设计并发控制结构(例如java.util.concurrent.locks包中的结构)时,它也可能很有用。
- 不确定性
yield方法只是一个提示性的方法,它不能保证当前线程一定会让出 CPU 资源,也不能保证其他线程一定会被选中执行。因此,在使用yield方法时,不能依赖它来实现特定的线程执行顺序。- 性能影响
- 频繁地调用
yield方法可能会对性能产生一定的影响。因为每次调用yield方法都会导致当前线程进入就绪状态,然后由调度器重新选择下一个要执行的线程,这个过程可能会消耗一定的时间和系统资源。- 优先级问题
yield方法并不能改变线程的优先级。如果一个线程的优先级较高,即使其他线程调用了yield方法,调度器仍然可能会优先选择高优先级的线程执行。
1 | public class Test { |
不保证一定让出 CPU,只是建议(JVM 可能忽略)。
不会阻塞线程(线程仍处于 RUNNABLE 状态)。
不释放锁。
通常用于调试或测试,生产代码中很少使用。