大家好,本博客致力于分享互联网领域的各种技术干货,欢迎关注我们一起交流一起学习哦!
一、背景
在Java的多线程编程中,线程调度与控制是核心内容之一。
在多线程环境下,多个线程可能同时竞争CPU资源或共享数据。这就引出了几个关键问题:
- 如何让线程在
某个时间段暂停
,避免过度占用资源? - 如何管理多个线程对共享资源的访问,避免
资源冲突
? - 如何在某些线程结束后,让其他线程及时
获得通知
并继续执行
?
为了解决这些问题,Java提供了诸如sleep()
、wait()
、notify()
等线程控制机制。本文将分析这些方法如何更好地管理线程的执行顺序、资源锁定和释放等复杂问题。
二、各个线程方法详解
1. sleep()
:暂停线程,不释放锁
sleep()
方法用于让当前线程进入阻塞状态,暂停执行一段时间。它的主要特点如下:
- 不释放锁:即使线程休眠,也不会释放同步块中的锁,其他线程无法进入该锁。
- 指定时间后恢复执行:线程会在指定时间后自动恢复,而不依赖其他线程的通知。
- 处理中断:如果线程在睡眠期间被中断,会抛出
InterruptedException
异常。
Thread.sleep(2000); // 暂停当前线程2秒
使用场景:需要让线程暂停一段时间以降低资源占用时使用,但要注意它不释放锁,可能阻塞其他线程的执行。
2. wait()
:进入等待状态,并释放锁
wait()
会让线程进入等待状态,并且释放它持有的锁。它只能在synchronized
块或方法中使用,且必须与notify()
或notifyAll()
配合使用来唤醒线程。
- 释放锁:
wait()
和sleep()
的最大区别是它会释放锁,让其他线程可以继续执行。 - 等待通知:线程进入等待状态后,必须等到其他线程调用
notify()
或notifyAll()
,或者设定的超时时间到达,线程才会被唤醒。
synchronized (lock) {
lock.wait(); // 当前线程进入等待状态,并释放锁
}
3. notify()
和 notifyAll()
:唤醒等待中的线程
notify()
和 notifyAll()
用来唤醒处于wait()
状态的线程。
notify()
只随机唤醒一个等待该锁的线程。notifyAll()
会唤醒所有等待该锁的线程,随后这些线程将继续争抢锁。
synchronized (lock) {
lock.notify(); // 唤醒一个等待的线程
}
使用场景:当需要通知一个或多个线程可以继续执行时使用,常与wait()
配合使用。
4. yield()
:让出CPU资源,不释放锁
yield()
使当前线程主动让出CPU资源,让系统调度其他线程执行。但它并不释放持有的锁,因此其他线程仍然无法访问同步块中的共享资源。
使用场景:在多线程环境下,yield()
可以用于让当前线程暂时放弃CPU资源,给其他线程执行机会,常用于性能调优场景。
5. join()
:等待其他线程结束
join()
用于让当前线程等待另一个线程执行完毕后再继续执行。如果线程A调用了线程B的join()
方法,那么A将进入阻塞状态,直到线程B执行完毕。
t.join(); // 当前线程等待t线程结束
join方法原理:
// 这是一个同步方法,主线程(调用线程)必须获取当前线程t的对象锁
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis(); // 记录开始时间
long now = 0; // 用于计算当前时间与基准时间的差
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 如果不传超时时间 (millis = 0),主线程将一直等待目标线程t完成
while (isAlive()) {
// 检查当前线程t是否还活着 (isAlive()返回true时表示线程还在执行)
// 进入等待,主线程(如main线程)进入阻塞状态并释放锁,直到目标线程t结束
wait(0);
// wait(0)表示无限期等待,直到目标线程t调用notifyAll()
}
} else {
// 如果传入了超时时间
while (isAlive()) {
// 持续检查目标线程t是否存活
long delay = millis - now; // 计算剩余的等待时间
if (delay <= 0) {
break; // 超时时间到了,跳出循环,不再等待
}
// 主线程等待指定时间,直到目标线程t结束或超时时间到
wait(delay);
now = System.currentTimeMillis() - base; // 更新已过去的时间
}
}
}
使用场景:常用于需要确保一个线程执行完后,才能执行下一个任务的场景。
关键区别与总结
三、关键区别与总结
通过以上分析,可以总结出这些方法的主要区别与应用场景:
方法 | 是否释放锁 | 是否释放CPU | 进入状态 | 唤醒方式 | 使用场景 |
---|---|---|---|---|---|
sleep() | 否 | 是 | 阻塞状态 | 自动/被中断 | 暂停执行 |
wait() | 是 | 是 | 等待状态 | notify() /notifyAll() | 条件等待 |
notify() | N/A | N/A | N/A | 唤醒一个等待线程 | 通知等待线程 |
notifyAll() | N/A | N/A | N/A | 唤醒所有等待线程 | 通知所有等待线程 |
yield() | 否 | 是 | 就绪状态 | 系统调度 | 让出CPU资源 |
join() | N/A | 是 | 阻塞状态 | 等待线程结束 | 线程协作 |
四、总结
Java中的线程控制方法提供了多种灵活的机制,用于管理线程的执行顺序、资源占用和同步问题。sleep()
和wait()
分别用于让线程暂停或等待,其中wait()
释放锁更适合同步场景,而sleep()
适用于简单的暂停需求。yield()
和join()
则更多用于线程之间的协作与调度控制。最后,notify()
和notifyAll()
则是wait()
的配套通知机制。
理解并正确使用这些方法是编写高效、多线程程序的关键。根据实际需求选择合适的线程控制方法,可以有效提升程序的执行效率与可靠性。