java中的wait和sleep

news/2025/3/12 14:15:37/

线程的状态

Java中线程的状态分为 6 种:

  1. 初始(NEW):新建了一个线程对象,但是还没调用start() 方法
  2. 运行(RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态统称为“运行”。线程对象在创建之后,其他线程(比如 main 线程)调用了该对象的 start() 方法,该状态的线程位于可变线程池中,等待被线程调度选中,获取CPU 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得 CPU时间片后变为运行状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞与锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定的动作(通知或者中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于 WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

wait

使用场景

获取到某个线程之后,发现当前并不需要使用该线程,则可以调用wait() 方法,使其进入等待过程。

等到某个时刻,满足条件之后,则可以使用notify() 或者 notifyAll() 方法来唤醒这个线程。

使用条件

对于已经获取到锁的线程才可以调用锁的wait(),notify()方法,否则会抛出IllegalMonitorStateException 异常。

比如下面的代码,A获得了锁之后,主动调用 wait() 方法释放锁和CPU资源,然后就进入了阻塞状态。

主线程在没有获得锁的情况之下,直接调用notify() 方法会抛出异常。

public class WaitTest {public static void main(String[] args) {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("被唤醒");}}, "A");threadA.start();lock.notify();}
}

输出结果:

现在我们创建一个线程B,抢占锁后,再调用 notify() 方法

public class WaitTest {public static void main(String[] args) {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("A 获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 被唤醒");}}, "A");threadA.start();Thread threadB = new Thread(() -> {synchronized (lock) {System.out.println("B 获取了锁");System.out.println("B 唤醒 A");lock.notify();}},"B");threadB.start();}
}

线程状态变化

首先我们需要知道,线程正常运行时的状态是 RUNNABLE,调用 wait() 方法后,变为 WAITING 状态。

举例说明:

public class WaitTest {public static void main(String[] args) {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("A 获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 被唤醒");}}, "A");threadA.start();try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait后的线程状态:"+threadA.getState());}
}

输出结果:

提问:主动 wait 的线程,被唤醒之后,它的状态一定是从 WAITING 变成 RUNNABLE 吗?

我们再做一个测试:A线程wait,2秒后,启动B线程,B线程去唤醒A线程。记录下唤醒前后的状态。

public class WaitTest {public static void main(String[] args) {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("A 获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());lock.wait();System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 被唤醒");}}, "A");threadA.start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}Thread threadB = new Thread(() -> {synchronized (lock) {System.out.println("B 获取到锁");System.out.println("叫醒A前,A的状态:"+threadA.getState());System.out.println("叫醒A");lock.notify();}},"B");threadB.start();}
}

输出结果:

上面的例子与我们的猜测是一致的。

但是,这段代码并不严谨,上述代码的 B线程在调用了 notify() 方法之后,立即释放了锁,但假如 B线程调用 notify() 后,还有很多任务没有完成,不立即释放锁,A线程的状态又会有什么变化?

修改后的代码:

public class WaitTest {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("A 获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());lock.wait();System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 被唤醒");}}, "A");threadA.start();TimeUnit.SECONDS.sleep(2);Thread threadB = new Thread(() -> {synchronized (lock) {System.out.println("B 获取到锁");System.out.println("叫醒A前,A的状态:"+threadA.getState());System.out.println("叫醒A");lock.notify();System.out.println("发现还有很多事要做,先不释放锁");System.out.println("B 在做其他事的时候,A 的状态:"+threadA.getState());try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("B 的事情都做完了");}},"B");threadB.start();}
}

输出结果:

通过观察我们发现,A 的状态并没有立刻变成 RUNNALBE,而是 BLOCKED,等到 B线程任务处理完释放锁之后,A 的状态才变成 RUNNALBE。

也就是说,B调用完 notify() 方法后,并不会立刻把线程的控制权交出去,A 被唤醒后,也不会立即将状态变成 RUNNABLE,而是先变成 BLOCKED,然后参与锁竞争,竞争成功重新获得锁之后,再向下执行。

Java中,每一个对象都有一个对应的 Monitor 锁,Monitor维护着 EntrySet 和 WaitSet。线程阻塞时,对象会被放入到 EntrySet 中,对应的状态是 BLOCKED。线程调用wait方法后,会被加入到WaitSet中,对应的状态是WAITING、TIMED_WAITIING。

上面我们分析过,线程“被唤醒”和“获得锁”是两个过程,被唤醒的线程需要重新参与锁竞争。

这么理解的话,那么线程应该是从 WaitSet 中苏醒后,又被加入到 EntrySet 队列。

有限等待

wait() 方法可以传入一个时间参数,是一种自动唤醒机制:在指定的时间内,如果没有其他线程唤醒自己,则主动唤醒自己。传 0 或者不传,则表示永久等待。

public class WaitTest {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("A 获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());System.out.println("当前时间:"+System.currentTimeMillis());lock.wait(1000);System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());System.out.println("当前时间:"+System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 被唤醒");}}, "A");threadA.start();}
}

输出结果:

这只是一种情况,如果 A 被唤醒后,继续参加锁的竞争,假设 A 在wait(t) 的过程中,有其他线程抢占了线程,A还能继续执行吗?

以下是代码示例:

public class WaitTest {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread threadA = new Thread(() -> {synchronized (lock) {System.out.println("A 获取到锁");try {System.out.println("休眠一会");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("调用wait");try {System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());System.out.println("当前时间:"+System.currentTimeMillis());lock.wait(100);System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());System.out.println("当前时间:"+System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 被唤醒");}}, "A");threadA.start();Thread threadB = new Thread(() -> {synchronized (lock) {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("A 的状态:"+threadA.getState());while (true) {}}},"B");threadB.start();}
}

输出结果:

即使 1000ms 过了,A 也不能继续执行,因为 A 只是被唤醒了,但是并没有成功获得锁,所以会进入 BLOCKED 状态。

sleep

Java中的线程休眠可以使用 Thread 类的静态方法 sleep()实现,该方法可以让当前线程暂停执行一段时间,以等待其他线程完成某些操作,或者为了节省系统资源而暂停线程执行。

代码示例:

public class SleepThreadExample {public static void main(String[] args) {System.out.println("线程开始执行");try {Thread.sleep(5000); // 休眠5秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程结束执行");}
}

在上面的代码中,我们使用sleep() 方法让线程休眠5 秒,然后再继续执行。如果在休眠期间线程被终端,就会抛出 InterruptedException异常。

需要注意的是,sleep() 方法会暂停当前线程的执行,因此如果在主线程中调用该方法,就会导致整个程序暂停执行,直到指定的时间到达。因此,我们应该避免在主线程中过渡使用sleep()方法,以免影响程序的响应性和用户体验。

sleep和wait的异同点

相同点

  1. 都可以使线程堵塞
  2. 都可以响应中断

不同点

  1. wait、notify方法必须写在同步方法中,是为了防止死锁和永久等待,使线程更安全,而sleep方法不需要有这个限制。
  2. wait方法调用后会释放锁,sleep方法调用后不会释放锁。
  3. sleep方法必须要指定时间参数,wait方法可以指定时间参数,但不是必需的
  4. 两个方法所属类不同,sleep方法属于Thread类;wait属于Object类中,放在Object类中是因为Java中每个类都可以是一把锁。

http://www.ppmy.cn/news/1164841.html

相关文章

arduino框架开发esp32,arduino框架的优势是什么?为什么要用这个框架

1,相比使用框架开发,Arduino框架的一个重要优点确实是它可以让开发者更专注于实现具体的功能,而不需要花费大量时间在底层硬件配置上。 例如, 我们开发stm32,我们使用keil开发工具,那么我们还需要stm32cube…

chromium 54 chrome 各个版本发布功能列表(109-119)

chromium Features 109-119 From https://chromestatus.com/features chromium109 Features:12 Auto range support for font descriptors inside font-face rule Auto range support for variable fonts in ‘font-weight’, ‘font-style’ and ‘font-stretch’ descrip…

vue 大文件切片下载

前提是你上传的时候也是切片上传,下载的时候后端给你返回的是一个文件id的数组,如果是你就可以用下面的方法 // 循环下载文件 // id是每个文件的id type 是一个类型,我传入是应为给不同的组件赋值getFile(id, type) {// 通过wen文件id去获取…

Java —— 程序逻辑控制

目录 1. 顺序结构 2. 分支结构 2.1 if 语句 2.1.1 语法格式1 2.1.2 语法格式2 2.1.3 语法格式3 2.2 switch 语句 3. 循环结构 3.1 while循环 3.2 break与continue 3.3 for循环 4. 输入输出 4.1 输出到控制台 格式化字符串 4.2 从键盘输入 5. 练习 和C语言类似地, Java的程序逻辑…

C# .Net6 指定WSDL, 生成Webservice,调用该接口服务

C# .Net6 指定WSDL, 调用该接口服务。 IDE: Microsoft Visual Studio Community 2022 (64 位)平台:.Net6协议:Soap协议 Xml格式 功能 需要开发一个前置机程序, 用于和硬件程序交互,已知条件是:嵌入式同事…

【vue】el-carousel实现视频自动播放与自动切换到下一个视频功能:

文章目录 一、原因:二、实现代码:三、遇到的问题:【1】问题:el-carousel页面的视频不更新【2】问题:多按几次左按钮,其中跳过没有播放的视频没有销毁,造成再次自动播放时会跳页 一、原因: 由于后端无法实现将多条视频拼…

SkyWalking 告警规则配置说明

Skywalking告警功能是在6.x版本新增的,其核心由一组规则驱动,这些规则定义在config/alarm-settings.yml 文件中。告警规则定义分为两部分: 1、告警规则:它们定义了应该如何触发度量警报,应该考虑什么条件 2、webhook(网络钩子):定义当告警触发时,哪些服务终端需要被…

RabbitMQ简单用法

ConnectionFactory ConnectionFactory类是RabbitMQ Java客户端库中的一个类,用于创建RabbitMQ连接。常用属性和方法如下: 属性: - host:RabbitMQ服务器的主机名,默认为localhost。 - port:RabbitMQ服务器…