难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.温故知新
前面在《“全栈2019”Java多线程第三十三章:await与signal/signalAll》一章中介绍了Condition对象的await()方法与signal()/signalAll()方法。
在《“全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程》一章中介绍了Condition对象的await(long time, TimeUnit unit)方法。
现在我们来讲解Condition对象的awaitNanos(long nanosTimeout)方法。
2.Object与Condition
实际上,当我们用显式锁Lock替代了synchronized时,Object的wait()、notify()和notifyAll()方法都被Condition对象中的方法所替代。
Object的wait()方法被Condition对象分解为以下几个方法:
- void await()
- boolean await(long time, TimeUnit unit)
- long awaitNanos(long nanosTimeout)
- void awaitUninterruptibly()
- boolean awaitUntil(Date deadline)
Object的notify()和notifyAll()方法被Condition对象分解为以下几个方法:
- void signal()
- void signalAll()
以上几个方法后面几章都会讲解。
3.获取线程被等待的时间awaitNanos(long nanosTimeout)方法
await()方法用在只需要使当前线程等待的场景;
await(long time, TimeUnit unit)方法用在当线程等待超时时自动唤醒的场景,而且还是判断出是人工唤醒还是自动唤醒,即当方法返回false时,是自动唤醒;当方法返回true时,是signal()/signalAll()方法唤醒。需要注意的是,中断线程并不会让程序正常往下执行,而是去执行catch或throws。
下面要为大家介绍一个等待家族的另一个方法:awaitNanos(long nanosTimeout)方法。
awaitNanos(long nanosTimeout)方法在Condition接口中的源码:
将注释翻译成中文:
中文注释全文:
造成当前线程在等待,直到它被唤醒,通常由被通知或中断或等待超时。去掉注释版:
awaitNanos(long nanosTimeout)方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断或等待超时。当返回值大于0时,说明是调用Condition的signal()/signalAll()方法主动唤醒被等待的线程,否则是等待超时,自动唤醒。
访问权限
long:awaitNanos(long nanosTimeout)方法返回long类型的值。
awaitNanos(long nanosTimeout)方法只能被对象调用。
参数
long nanosTimeout:等待的最长时间,以纳秒为单位。
抛出的异常
throws InterruptedException:如果当前线程被中断(并且支持中断线程挂起)。
应用
首先,我们可以看到awaitNanos(long nanosTimeout)方法可以使当前线程等待,它还有一个long类型的参数:
这个参数的意思是设置线程最长等待时间,单位是纳秒。由此可见,线程在调用awaitNanos(long nanosTimeout)方法进行等待时,是有一个最长等待时间的,超过这个时间,被等待的线程就会被唤醒。
同时还注意到,awaitNanos(long nanosTimeout)方法有一个返回值:
返回值类型是long,它有何作用?等我们先试试awaitNanos(long nanosTimeout)方法的使当前线程等待效果再说。
我们先创建出实现了Runnable接口的匿名内部类对象:
不着急完善run()方法。
接着,我们创建出线程,此处创建出3个线程:
然后,启动线程:
接着,我们来完善run()方法。
因为需要同步,所以需要显式锁Lock对象:
然后,把同步(获取/释放锁)写好:
接着,获取Condition对象:
然后,在获取锁之后使线程进行等待,此处等待3秒(1秒=1000000000纳秒):
接着,awaitNanos(long nanosTimeout)方法会抛出异常,这里我们使用catch将其处理:
在处理代码块中输出一句话:
最后,我们在等待前后各输出一句话,目的就是为了看线程何时被等待以及何时被唤醒:
例子写好了。
运行程序,执行结果:
静图:
文字版:
Thread-0 --- 等待 Thread-2 --- 等待 Thread-1 --- 等待 Thread-1 --- 被唤醒-3549194 Thread-2 --- 被唤醒-22028203 Thread-0 --- 被唤醒-22501430从运行结果来看,符合预期。3个线程在等待了3秒钟之后自动被唤醒。
但是,我们发现结果中被唤醒三个字后面有一个横线加上一串数字:
首先,上例中的这个“-”不是横线,它是负号,其次-3549194它是awaitNanos(long nanosTimeout)方法的返回值。
为什么是负数呢?
先来看一个动画,有助于我们理解:
是这样的,awaitNanos(long nanosTimeout)方法会让你传一个线程最长等待时间(单位为纳秒):
然后线程自己也有记录一个从等待那一刻开始一直到被唤醒或被中断的时间,注意,唤醒也是要时间的:
最长等待时间 - 线程已等待的时间 = awaitNanos(long nanosTimeout)方法返回值:
于是就得到了上例中的-3549194结果:
看到这里,可能大家已经知道如何获取线程被等待的时间了,就是:
线程被等待的时间 = 线程最长等待时间 - awaitNanos(long nanosTimeout)方法返回值
我们来修改程序,算出每个线程被等待的时间。
这里我们需要借助时间单位TimeUnit类帮忙,将秒转为纳秒。调用TimeUnit.SECONDS.toNanos(long duration)方法即可,参数duration是需要转换的秒数:
然后,我们将转换好的纳秒数传递给awaitNanos(long nanosTimeout)方法:
接着,我们用线程最长等待时间 - awaitNanos(long nanosTimeout)方法返回值得到线程等待时间:
然后,我们将其输出:
注意:awaitNanos(long nanosTimeout)返回值单位还是纳秒,所以计算结果的单位依然是纳秒。
运行程序,执行结果:
从运行结果来看,符合预期。只不过,这单位看得有点不习惯。如果将纳秒转换为秒,做除法的话容易丢失精度,无特殊需求还是纳秒作为单位为好。
4.中断线程
被awaitNanos(long nanosTimeout)方法等待的线程可以被中断。
下面,我们就来试试。
还是上一小节的例子,中断被等待线程需要持有被等待的锁,于是,我们需要主线程去竞争该显式锁:
在获取锁之前,我们需要主线程睡1秒钟,目的是为了让上面3个线程先去拿到锁,然后被等待:
最后,调用3个线程对象的interrupt()方法使线程被中断:
例子改写完毕。
运行程序,执行结果:
从运行结果来看,符合预期。被中断的线程只会去执行catch代码块或throws。
5.唤醒被等待的线程signal()方法/signalAll()方法
还是上一小节的例子,只不过,我们将中断线程换成signal()/signalAll()方法。
下面,我们先将中断线程的代码移除,即以下代码:
然后,调用Condition对象的signal()/signalAll()方法:
例子改写完毕。
运行程序,执行结果:
静图:
文字版:
Thread-0 --- 等待 Thread-1 --- 等待 Thread-2 --- 等待 Thread-0 --- 被唤醒968571782 Thread-1 --- 被唤醒982853904 Thread-2 --- 被唤醒982915342从运行结果来看,符合预期。而且线程的等待时间也知道了,是968571782纳秒,大约是1秒钟。
不过,我们可以来看下此时的awaitNanos(long nanosTimeout)方法返回值是正是负。
修改程序代码,输出awaitNanos(long nanosTimeout)方法返回值:
运行程序,执行结果:
从运行结果来看,符合预期。awaitNanos(long nanosTimeout)方法返回值此时为正数,和之前自动唤醒时的结果不同,自动唤醒时的结果是负数。
为什么自动唤醒时awaitNanos(long nanosTimeout)方法返回值为负数,主动调用Condition对象的signal()/signalAll()方法唤醒线程时awaitNanos(long nanosTimeout)方法返回值为正数?
道理其实很简单,假设我们的线程最长等待时间为3000000000纳秒(即3秒钟),在3秒钟之内唤醒被等待的线程,结果肯定是正数,因为线程已等待时间是在3000000000纳秒(即3秒钟)之内,3000000000 - 3000000000之内的数(不包括3000000000)结果肯定大于0;在3秒或3秒之外唤醒被等待的线程,结果肯定是0或负数,因为线程已等待时间是在3000000000纳秒或3000000000纳秒之外,3000000000 - 大于或等于3000000000的数结果肯定小于或等于0;
6.await(long time, TimeUnit unit)方法与awaitNanos(long nanosTimeout)方法
在上一章《“全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程》中,我们得知await(long time, TimeUnit unit)等同于awaitNanos(long nanosTimeout) > 0。
当awaitNanos(long nanosTimeout)返回值大于0时,就是我们主动调用Condition对象的signal()/signalAll()方法唤醒被等待的线程,即await(long time, TimeUnit unit)方法返回true;否则就是超过被等待的线程最长等待时间,自动唤醒。
最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。
祝大家编码愉快!
GitHub
本章程序GitHub地址:
总结
- await()方法使当前线程在等待,直到它被唤醒,通常由被唤醒或中断。
- await(long time, TimeUnit unit)方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断或等待超时。当线程因超时自动唤醒时方法返回false,被通知唤醒返回true。
- awaitNanos(long nanosTimeout)方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断或等待超时。当返回值大于0时,说明是调用Condition的signal()/signalAll()方法主动唤醒被等待的线程,否则是等待超时,自动唤醒。
- 线程被等待的时间 = 线程最长等待时间 - awaitNanos(long nanosTimeout)方法返回值
- 当awaitNanos(long nanosTimeout)返回值大于0时,就是我们主动调用Condition对象的signal()/signalAll()方法唤醒被等待的线程,即await(long time, TimeUnit unit)方法返回true;否则就是超过被等待的线程最长等待时间,自动唤醒。
- signal()方法唤醒正在此对象监视器上等待的单个线程,选择是随机的。
- signalAll()方法作用是唤醒正在此对象监视器上等待的所有线程。
至此,Java中Condition对象的awaitNanos(long nanosTimeout)方法相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程
下一章
“全栈2019”Java多线程第三十六章:如何设置线程的等待截止时间
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!