难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.温故知新
前面在 一章中介绍了 Lock获取lock/释放unlock锁。
在 一章中介绍了 公平锁与非公平锁 。
在 一章中介绍了 可重入锁与不可重入锁 。
在 一章中介绍了 Lock尝试获取锁tryLock()方法 。
现在我们来讲解 显式锁Lock中的lockInterruptibly()方法:在如果当前线程没被中断,则一直获取锁。
2.隐式锁上被等待的线程无法被中断
隐式锁在 一章中介绍过了,不清楚的小伙伴可以前去查阅。
中断: 调用线程的interrupt()方法 。
下面演示隐式锁上被等待的线程无法被中断的例子。
先写一个匿名内部类实现Runnable接口的对象:
先不着急完善run()方法。
接着创建3个线程:
随后将启动线程的代码写了:
再来完善run()方法。
在run()方法内部写上同步代码块:
然后,在同步代码块内部写上输出语句:
暂时先运行程序看看效果。
运行程序,执行结果:
从运行结果来看,符合现阶段预期。
如果要想到达中断正在等待同步锁的线程的效果,还需让主线程拿到锁。
为什么需要让主线程拿到锁?
如果其他线程正在等待同步锁,说明同步锁已经被线程拿走了,所以才会等待。
此时我们让主线程拿到锁,再在同步代码块里面启动其他线程,然后中断它们即可。
代码怎么写?
首先,我们在启动线程前让主线程拿到锁:
然后,将启动线程的代码移到同步代码块中:
接着,写上输出语句:
然后,为了表示主线程确实拿到锁了,让主线程睡3秒钟再释放锁:
代码先写到这,运行程序,执行结果:
从运行结果来看,符合当前预期。
下面,我们只需将等待锁的其中一个线程中断即可:
运行程序,执行结果:
从运行结果来看,符合预期。 隐式同步锁上所有正在等待的线程无法被中断。
3.显式锁上被等待的线程无法被中断
不光是隐式同步锁上所有正在等待的线程无法被中断,而且显式同步锁上所有正在等待的线程也无法被中断。
之前在 一章中介绍了 Lock获取lock/释放unlock锁, 即Lock的lock()方法可以获取锁。
改写上一小节的例子。
首先,我们将显式锁创建出来:
然后,将匿名内部类runnable对象里面run()方法内部同步代码块开头和结尾用lock()和unlock()方法替代掉。
需要替代的代码:
替代之后的代码:
接着,主线程中的同步代码块也需要替代,需要替代的代码:
替代之后的样子:
例子用显式锁改写完毕。
运行程序,执行结果:
从运行结果来看,符合预期。 显式同步锁上所有正在等待的线程无法被中断。
但是,显式锁比隐式锁更加灵活一点是: 用显式锁的lockInterruptibly()方法去获取锁造成线程等待的,可以被中断。
下面,我们通过lockInterruptibly()方法来改写该例子。
4.获取锁lockInterruptibly()方法
lockInterruptibly()方法也可以用于获取锁。
lockInterruptibly()方法在Lock接口中的源码:
将注释翻译成中文:
中文注释全文:
如果当前线程未被中断,则一直获取锁。
去掉注释版:
调用lockInterruptibly()方法时,如果当前线程未被中断,则一直获取锁。
访问权限
void :lockInterruptibly()方法无返回值。
lockInterruptibly()方法只能被对象调用。
参数
无。
抛出的异常
throws InterruptedException :如果当前线程在获取锁时被中断,则抛出此异常。
应用
lockInterruptibly()方法抛出了一个InterruptedException异常,InterruptedException是线程被中断时抛出的,说明正在等待同步锁的线程可以被中断。
下面使用lockInterruptibly()方法来改写上述例子。
我们只需将匿名内部类runnable对象中的run()方法内部lock.lock()改写为lock.lockInterruptibly()即可:
lockInterruptibly()会抛出 InterruptedException 异常,我们将其catch即可:
处理异常上,我们输出当前线程名称,大家也可以自己根据需求而定:
例子用lockInterruptibly()方法改写完毕。
运行程序,执行结果:
静图:
文字版:
main --- run Thread-0 --- 被中断 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:149) at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1302) at java.base/java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:439) at main.Main$1.run(Main.java:31) at java.base/java.lang.Thread.run(Thread.java:834) Thread-2 --- run Thread-1 --- run
从运行结果来看,程序在31行代码处发生异常:
即释放锁这里,异常发生的原因也很简单,因为中断线程导致发生 InterruptedException 异常,在处理完 InterruptedException 异常后,finally代码块是一定会被执行的,所以此时lock.unlock()方法会被执行。
又因为被中断的线程并没有获取到锁,拿什么锁释放呢?它没有锁,却调用了unlock()方法,所以产生了 IllegalMonitorStateException 异常。
有什么办法可以解决?
这里我们可以通过一个boolean类型的变量来解决。
我们在匿名内部类runnable中定义一个boolean类型的变量locked,用来记录线程是否已经获取到锁:
当线程获取到锁时,将locked变量置为true:
在finally代码块中,当locked为true时,释放锁:
并将locked变量置为false:
例子改写完毕。
运行程序,执行结果:
从运行结果来看,符合预期。程序没有抛出异常。
这里,我们将其他两个线程也都中断:
运行程序,执行结果:
从运行结果来看,中断所有正在等待的线程也没有问题。
于是lock()方法和lockInterruptibly()方法的区别可以总结以下几点:
- lock()方法不抛异常;lockInterruptibly()方法抛异常。
- 调用lock()方法时,正在锁上等待的线程无法被中断;调用lockInterruptibly()方法时,正在锁上等待的线程可以被中断。
最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。
祝大家编码愉快!
GitHub
本章程序GitHub地址:
总结
- 隐式锁上被等待的线程无法被中断。
- 显式锁上被等待的线程无法被中断。
- lockInterruptibly()方法也可以用于获取锁。
- 调用lockInterruptibly()方法时,如果当前线程未被中断,则一直获取锁。
lock()方法和lockInterruptibly()方法的区别可以总结以下几点:
- lock()方法不抛异常;lockInterruptibly()方法抛异常。
- 调用lock()方法时,正在锁上等待的线程无法被中断;调用lockInterruptibly()方法时,正在锁上等待的线程可以被中断。
至此,Java中lockInterruptibly()方法相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
下一章
“全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!