2019全年技能Java多线程指南:深入理解tryLock锁定机制的第三十章节
难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.温故知新
前面在《“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁》一章中介绍了Lock获取lock/释放unlock锁。
在《“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解》一章中介绍了公平锁与非公平锁。
在《“全栈2019”Java多线程第二十九章:可重入锁与不可重入锁详解》一章中介绍了可重入锁与不可重入锁。
现在我们来讲解Lock尝试获取锁tryLock()方法。
2.获取锁lock()方法
之前在《“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁》一章中介绍了Lock获取lock/释放unlock锁,即Lock的lock()方法可以获取锁。
下面,我们先来用lock()方法获取锁,然后用tryLock()方法改写,最后比较它们有什么不同。
先写一个匿名内部类实现Runnable接口的对象:
先不着急把run()方法写好,先来把线程创建好。
这里创建3个线程,并把runnable对象传递给线程:
紧接着,我们启动线程的代码也写了:
接下来,我们来完善run()方法内部。
在run()方法内部需要做的是,执行一个同步域(即同步代码块/方法或显式锁锁定的代码)。
在此之前,先把显式锁创建出来:
然后,在run()方法内部写上同步:
接着,在同步中写上输出语句,格式是当前线程名称+其他内容:
然后,我们让几个线程一直不停的执行同步,所以在同步外面嵌套while循环:
为了让输出语句输出的慢一点,我们每隔一秒输出一次,即让当前线程在输出前睡眠1秒钟:
好了,例子书写完毕。
运行程序,执行结果:
从运行结果来看,符合预期。
该例子展示了使用lock()方法获取锁。
下面,我们通过tryLock()方法来改写该例子。
3.尝试获取锁tryLock()方法
我们可以使用Lock实现类实例的tryLock()方法来尝试性获取锁。
tryLock()方法在Lock接口中的源码:
将注释翻译成中文:
中文注释全文:
获取锁定(如果可用)并立即返回值true。 如果锁定不可用,则此方法将立即返回值false。
此方法的典型用法是:
Lock lock = ...;
if (lock.tryLock()) {
try {
// 需要同步的内容
} finally {
lock.unlock();
}
} else {
// 当没有获取到锁之后的操作
}
此用法可确保锁定在获取时解锁,并且如果未获取锁定,则不会尝试解锁。
去掉注释版:
调用tryLock()方法时,如果锁可用,则获取锁,方法返回true;如果不可用,方法返回false。
访问权限
boolean:tryLock()方法返回boolean类型值,如果锁可用,则获取锁,方法返回true;如果不可用,方法返回false。
tryLock()方法只能被对象调用。
参数
无。
抛出的异常
无。
应用
tryLock()方法是有获取锁的能力的,所以我们可以用tryLock()方法替代lock()方法,但是它们之间还是有区别的:lock()方法是获取不到锁时等待;tryLock()方法是尝试去获取锁,如果获取到锁返回true,否则返回false,不会造成当前线程等待。
下面就通过实战来看下tryLock()方法怎么用。
首先,还是之前的例子,只不过先把lock.lock()方法去掉,也就是这行代码:
去掉之后的样子:
随后,我们加上tryLock()方法:
因为tryLock()方法有返回值,且返回值类型是boolean类型,所以官方也建议我们使用tryLock()方法时配搭if-else语句。
下面就要写else语句里面的内容,即当没有获取到锁的时候怎么办:
这里我们处理方式是让没有获取到锁的线程睡1秒钟,之后再输出当前线程名称+其他语句。当然了,大家也可以有自己的处理方式。
接下来,运行程序,执行结果:
从运行结果来看,符合预期,线程如约拿到锁并且在执行完任务之后释放锁。
但是,我们发现锁本来是公平锁,怎么看起来又不公平了呢?从头到尾都是“Thread-1”线程在运行:
这是因为我们没有拿到锁的线程都睡了1秒钟:
正是这1秒钟导致错过获取锁的最佳机会,下面我们可以通过让拿到锁的线程在释放锁之后睡2秒钟,目的为了证明我们的锁是公平锁:
再次运行程序,观察执行结果:
从运行结果来看,这次每个线程都得到了锁。
以上是在while循环里面使用tryLock()方法,如果去掉while循环会怎么样呢?
来看看,我们把while循环去掉:
运行程序,执行结果:
所以线程只跑了一次,Thread-1和Thread-2线程都没抢到锁,只有Thread-0抢到锁了。
4.一段时间内尝试获取锁tryLock(long time, TimeUnit unit)方法
tryLock(long time, TimeUnit unit)方法和tryLock()方法一样都是尝试获取锁,只不过tryLock(long time, TimeUnit unit)方法增加了一个特性,这个特性就是在一段时间内线程一直会去尝试获取锁。而tryLock()方法只会尝试一次去获取锁。
tryLock(long time, TimeUnit unit)方法在Lock接口中的源码:
将注释翻译成中文:
中文注释全文:
如果在给定的等待时间内锁空闲并且当前线程未被中断,则获取锁。
如果已获取锁,则为true;如果在获取锁之前已经过了等待时间,则为false。
去掉注释版:
调用tryLock(long time, TimeUnit unit)方法时,如果锁可用,则获取锁,方法返回true;如果不可用,则线程等待,直到锁可用或线程被中断,当超过等待时间还没有获取到锁时,方法返回false。
访问权限
boolean:tryLock(long time, TimeUnit unit)方法返回boolean类型值,如果锁可用,则获取锁,方法返回true;如果不可用,则线程等待,直到锁可用或线程被中断,当超过等待时间还没有获取到锁时,方法返回false。
tryLock(long time, TimeUnit unit)方法只能被对象调用。
参数
time:等待锁的最长时间。
unit:时间参数的时间单位。
抛出的异常
throws InterruptedException:如果当前线程在获取锁时被中断,则抛出此异常。
应用
用tryLock(long time, TimeUnit unit)方法替代上一小节例子中的tryLock()方法:
不过,tryLock(long time, TimeUnit unit)方法会抛出异常,所以我们要在if-else语句外面套一层try-catch或单独放一行:
这里需要注意的是,我们time传递的是1,unit传递的是TimeUnit.HOURS(即小时),结合起来就是1小时之内都会去等待尝试获取锁。当然了,还有其他时间单位,大家可以根据自己的需求而选择合适的时间单位。
下面我们就来看看实际运行效果,运行程序,执行结果:
从运行结果来看,符合预期。每个线程都拿到锁都运行了。
对比之前的情况一目了然,下面是之前的运行结果,请大家对比着看:
最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。
祝大家编码愉快!
GitHub
本章程序GitHub地址:https://github.com/gorhaf/Java2019/tree/master/Thread/Lock
总结
- 调用tryLock()方法时,如果锁可用,则获取锁,方法返回true;如果不可用,方法返回false。
- 调用tryLock(long time, TimeUnit unit)方法时,如果锁可用,则获取锁,方法返回true;如果不可用,则线程等待,直到锁可用或线程被中断,当超过等待时间还没有获取到锁时,方法返回false。
- lock()方法是获取不到锁时等待;tryLock()方法是尝试去获取锁,如果获取到锁返回true,否则返回false,不会造成当前线程等待。
至此,Java中尝试获取锁tryLock()方法相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java多线程第二十九章:可重入锁与不可重入锁详解
下一章
“全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!