Java之sleep和wait
Posted 风在哪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java之sleep和wait相关的知识,希望对你有一定的参考价值。
Java之sleep和wait
sleep和wait方法都是native关键字修饰的方法,这说明这两个方法是原生函数,也就是由C/C++实现的,那么我们就暂时不关心它的具体实现了。
sleep方法是Thread类中的方法,而wait方法是Object中的方法,那么我们首先看看wait方法。
Object#wait()
从Object源码中,我们可以发现,wait有三个重载方法,分别是无参的wait方法,带有long和int类型参数的的wait方法,以及带有long类型参数的方法。其实前两个方法最终都是调用了wait(long)方法,而wait(long)方法是native修饰的方法,它底层就是由C++实现的,这里暂且不讨论,我们来看看这个方法的注释。
/*
wait方法会导致当前线程等待,直到其他线程调用notify和notifyAll方法,或者达到了指定的等待时间
使用wait方法的前提是当前线程拥有该对象的监视器也就是锁
该方法会导致当前线程T(调用wait方法的线程)将自己放到该对象的等待集合中,然后会放弃此对象上的所有同步声明,也就是会放弃对象的锁
该等待线程不会被调度并且处于休眠状态,直到以下四种情况之一发生:
1、其他线程调用等待对象notify方法,并且当前线程T被随机选为要唤醒的方法时,线程将会退出休眠状态
2、其他线程调用等待对象的notifyAll方法
3、其他线程中断当前线程T
4、超过指定的等待时间。如果等待时间为0的话,时间因素将不会被考虑,那线程将等待直到被通知唤醒
当发生上述四种情况时,线程T将会从该对象的等待集合中移除,并且可以重新被调度。然后它以通常的方式与其他线程竞争对象上的同步锁,一旦它获得了对对象的控制权,它对对象的所有同步声明将会恢复到原来的状态,也就是说,恢复到调用wait方法时的状态。然后线程T将会从调用wait方法的方法中返回。因此,从wait方法返回时,对象和线程T的同步状态与调用wait方法时的状态完全相同。
如果当前线程在等待之前或者等待时被中断,会抛出InterruptedException异常。
注意,等待方法将当前线程防止到该对象的等待集合时,只解锁此对象;在线程等待时,当前线程同步的其他任何对象都将保持锁定状态。
wait方法仅能被持有对象监视器的线程调用(对象监视器就相当于对象的锁)
通过如下方法可以获得对象的监视器:
1、通过执行该对象的同步方法(也就是synchronized关键字修饰的方法)
2、通过执行该对象的同步代码块(synchronized(Object) {})
3、通过执行类的同步静态代码块(也就是synchronized关键字修饰的静态方法)
*/
public final native void wait(long timeout) throws InterruptedException;
通过wait方法的注释,我们可以发现,wait方法有如下作用:
- 使线程进入休眠状态,不被调度,直到被notify方法选中或者notifyAll方法的执行,才会被唤醒
- 线程会释放调用wait方法的对象的锁(但是不会释放线程持有的其他对象的锁),这样其他线程可以竞争该对象的锁
- 从wait方法中退出后,线程会回到调用该方法时的状态
既然要notify和notifyAll方法才能唤醒调用wait方法陷入等待的线程,那么我们看看这两个方法的注释:
/*
唤醒一个等待对象锁的线程
如果有多个线程在等待该对象,会随机唤醒一个线程
在当前线程放弃该对象的锁之前,唤醒的线程将无法继续执行
唤醒的线程将以通常的方式与其他线程竞争,这些线程会公平地在这个对象上进行同步竞争。例如,被唤醒的线程在竞争对象的锁时没有特权或者缺点
该方法仅在线程持有对象的监视器时才能被调用,获取对象的监视器有如下方法:也就是synchronized关键字修饰的方法、静态方法以及代码块等
*/
public final native void notify();
/*
唤醒所有等待该对象监视器的线程
在当前持有对象锁的线程放弃对象锁之前,被唤醒的线程无法执行
*/
public final native void notifyAll();
通过方法的注释来看,这两个方法就是用于唤醒等待该对象的线程,notify随机唤醒一个线程,notifyAll会唤醒全部线程,这些被唤醒的线程处于就绪态,它们会和正在运行的线程一起抢占对象的锁,得到对象的锁之后才能继续执行。
通过JDK源码的注释,我们对wait和notify方法有了更进一步的了解,那么接着看看sleep方法,才能知道wait和sleep的区别
Thread#sleep()
Thread类中的sleep方法也有两个重载方法,其中sleep(long)是底层的实现。
来看看它们的注释:
/*
根据系统计时器和调度程序的精度和准确性,使当前执行的线程休眠(暂时停止执行)指定的毫秒数
线程不会释放持有的锁
该方法响应中断,当遇到中断时会抛出InterruptedException异常,并且会清除当前线程的中断状态
*/
public static native void sleep(long millis) throws InterruptedException;
/*
使当前执行的线程休眠(临时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统计时器和调度程序的精度和准确性
线程不会释放持有的锁
nanos的范围:0-999999
*/
public static void sleep(long millis, int nanos)
throws InterruptedException {
// 判断millis和nanos是否符合条件
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
// 调用原生sleep方法
sleep(millis);
}
wait()和sleep()方法区别
通过源码的注释我们可以发现wait()方法和sleep()方法的一些区别:
- 最容易发现的就是wait方法属于Object类,sleep方法属于Thread类
- wait方法被调用后会释放持有的对象上的锁,而sleep方法不会释放对应的锁
- wait方法和sleep方法都响应中断,但是两者响应中断之后的操作略有不同,wait方法响应中断抛出异常之后不会清除线程的中断状态,而sleep方法响应中断抛出异常之后会清除线程的中断状态
- wait、notify、notifyAll方法只能在同步方法或者同步代码块中使用(wait和notify方法必须在synchronized修饰的方法或代码块中调用,如果在非同步的代码块中调用,会产生IllegalMonitorStateException异常),而sleep在任何地方使用
- sleep方法必须捕获异常,而wait方法不需要捕获异常
- 调用wait方法后,必须调用notify或者notifyAll方法唤醒等待的线程,如果线程持有对象的锁,那么会释放该对象的锁,但是不会释放持有的其他锁
- 一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。
Thread#yield()和Thread#join()方法
yield方法就是当前线程主动让处CPU,让其他同等优先级的线程获得执行机会。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
/*
对调度程序的提示,表示当前线程愿意放弃当前对处理器的使用,调度程序可以忽略该提示。
yield是一种启发式尝试,旨在改善线程之间的执行方式,避免某个线程过度使用CPU
它的使用应该与详细的分析和基准测试相结合,以确保它实际具有预期的效果。
使用这种方法很少合适。它可能对调试或测试有用,因为它可能有助于再现由于竞争条件而产生的bug。
*/
public static native void yield();
从源码的注释来看,yield方法在我们的实际开发中,使用的场景可能不是很多,因为即使使用了也可能无法达到想要的效果,所以,我们需要谨慎使用
接下来看看join方法,join方法用于在某一个线程执行过程中调用另一个线程执行,等到被调用的线程执行结束后,会继续执行当前线程,也就是等待某个线程执行结束后会再执行当前线程
那么这是怎么实现的呢?我们可以发现,在调用join方法之后,join方法会判断当前线程是否存活,如果存活的话,会调用wait(0)方法,让线程一直等待。当线程结束的时候,会调用notifyAll方法,让等待的线程被唤醒,等待被调度。这是在JVM层面实现,JVM中在线程执行结束exit后,会调用notifyAll方法,这也是为什么注释中建议应用程序不要在线程实例上使用wait、notify和notifyAll方法
/*
等待此线程死亡的时间最多为毫秒,0意味着永远等待
此实现使用以this.isAlive()为条件的this.wait的while循环,当线程终止时,会调用notifyAll方法
建议应用程序不要在线程实例上使用wait、notify和notifyAll方法
该方法会响应中断,并且将在抛出异常后会清除线程的中断状态
*/
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) {
// isAlive判断当前线程是否还存活,如果存活的话就调用wait方法
while (isAlive()) {
wait(0);
}
} else {
// 如果当前线程还处于存活状态
while (isAlive()) {
// 计算实际的等待时间,调用wait方法
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
总结
通过jdk源码的注释,我们可以学习到好多东西!
以上是关于Java之sleep和wait的主要内容,如果未能解决你的问题,请参考以下文章
java之sleepwaityieldjoinnotify乱解
Java多线程之sleep,wait,join和yield关键字,以及线程的关闭
Java并发之wait notify yield sleep join
java之yield(),sleep(),wait()区别详解