当前位置:网站首页>【面试:并发篇22多线程:ReentrantLock】
【面试:并发篇22多线程:ReentrantLock】
2022-07-23 18:06:00 【I cream】
【面试:并发篇22多线程:ReentrantLock】
00.前言
如果有任何问题请指出,感谢。
01.介绍
RennttrantLock也是锁和synchronized一样具有锁定同步代码块的作用,不过和synchronized还是有很多不一样的地方。
RennttrantLock是java.util.concurrent(juc) 下的一个类,相对于synchronized它具备如下特点:
1.可以打断处于BLOCKED状态的其他线程
2.可以设置处于BLOCKED状态的线程超时时间,如果超过这个时间就放弃争抢锁
3.可以设置为公平锁,线程按照先入先出获取锁
4.支持多个条件变量,这个是指 对于等待区 我们可以由多个 不同对象调用锁处于等待状态时所在的等待区不同,对于synchronized锁来说 它的等待区就只有一个WaitSet
与synchronized一样 都支持锁重入,即自己可以再次获取自己内部的锁
02.基本语法
ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
// lock.lock();
// 临界区
}finally{
// 释放锁
lock.unlock();
}
注意:lock.lock();可以放在try外面,也可以放在try里面,最后在finally中需要进行lock.unlock();释放锁
03.可锁重入
例子
public class TestInterKCR {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
System.out.println("enter main");
m1();
}finally {
lock.unlock();
}
}
private static void m1(){
lock.lock();
try {
System.out.println("enter m1");
m2();
}finally {
lock.unlock();
}
}
private static void m2(){
lock.lock();
try {
System.out.println("enter m2");
}finally {
lock.unlock();
}
}
}
结果
enter main
enter m1
enter m2
解释
可以看出自己内部的锁都可以调用,没有发生死锁,说明可以锁重入
04.可打断
介绍
我们先要明确一个概念,这里说的可打断 说的是 一个已经获得锁的线程t1 和一个处于阻塞状态线程的t2(因为t1已经获取了锁),我们可以在其他线程中 把 t2线程打断 使之 跳出阻塞状态 运行其它部分代码。
synchronized为什么不可以打断
例子
public class TestInterSync {
private static String a = "s";
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("t1尝试获取锁");
synchronized (a){
System.out.println("t1获取到锁");
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted){
System.out.println("t1被打断");
}
}
});
Thread t2 = new Thread(()->{
System.out.println("t2尝试获取锁");
synchronized (a){
System.out.println("t2获取到锁");
while (true){
}
}
});
t2.start();
Sleeper.sleep(0.5);
t1.start();
Sleeper.sleep(0.5);
System.out.println("打断正在等待锁的t1线程");
t1.interrupt();
}
}
结果
程序没有结束:
t2尝试获取锁
t2获取到锁
t1尝试获取锁
打断正在等待锁的t1线程
解释
我们发现程序并没有结束,我们的t2线程先获取到锁且没有释放锁,导致t1线程阻塞,现在我们在主线程中打断t1线程 但是发现无法打断
lock方法可以打断吗?
例子
public class TestInterReenY {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("t1尝试获取锁");
lock.lock();
try {
System.out.println("t1获取到锁");
}finally {
lock.unlock();
}
});
Thread t2 = new Thread(()->{
System.out.println("t2尝试获取锁");
lock.lock();
try {
System.out.println("t2获取到锁");
Sleeper.sleep(2);
}finally {
}
});
t2.start();
Sleeper.sleep(0.5);
t1.start();
Sleeper.sleep(0.5);
System.out.println("打断正在等待锁的t1线程");
t1.interrupt();
}
}
结果
程序没有结束:
t2尝试获取锁
t2获取到锁
t1尝试获取锁
打断正在等待锁的t1线程
解释
我们发现lock方法依然不能打断处于阻塞状态的线程,那我们应该用什么,答案是lockInterruptibly方法
lockInterruptibly方法
例子
public class TestInterReen {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
System.out.println("t1尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("t1被打断");
return;
}
try {
System.out.println("t1获取到锁");
}finally {
lock.unlock();
}
});
Thread t2 = new Thread(()->{
try {
System.out.println("t2尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("t2被打断");
return;
}
try {
System.out.println("t2获取到锁");
Sleeper.sleep(2);
}finally {
}
});
t2.start();
Sleeper.sleep(0.5);
t1.start();
Sleeper.sleep(0.5);
System.out.println("打断正在等待锁的t1线程");
t1.interrupt();
}
}
结果
程序结束:
t2尝试获取锁
t2获取到锁
t1尝试获取锁
打断正在等待锁的t1线程
t1被打断
解释
可以看出我们用lockInterruptibly方法可以实现 打断处于阻塞状态的线程
05.锁超时
介绍
锁超时是指,一旦锁被其他线程占用 如果没有指定超时时间 则本线程会直接跳出阻塞状态 不参与竞争,如果指定了超时时间 则 超时时间内处于阻塞状态 超出超时时间 则跳出阻塞状态。
例子:无时限
@Slf4j(topic = "c.TestInterCS")
public class TestInterCS {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.debug("t1尝试获取锁");
if (!lock.tryLock()){
log.debug("t1获取不到锁");
return;
}
try {
log.debug("t1获取到锁");
}finally {
}
},"t1");
Thread t2 = new Thread(()->{
log.debug("t2尝试获取锁");
if (!lock.tryLock()){
log.debug("t2获取不到锁");
return;
}
try {
log.debug("获取到锁");
}finally {
lock.unlock();
}
},"t2");
t1.start();
Sleeper.sleep(1);
t2.start();
}
}
结果
00:40:27.595 c.TestInterCS [t1] - t1尝试获取锁
00:40:27.597 c.TestInterCS [t1] - t1获取到锁
00:40:28.600 c.TestInterCS [t2] - t2尝试获取锁
00:40:28.600 c.TestInterCS [t2] - t2获取不到锁
解释
可以看出t1线程获取到锁并且没有释放,t2线程一直处于阻塞状态,我们用tryLock方法 且没有指定超时时间 也就是一旦发现t2线程处于阻塞状态lock.tryLock()返回false,我们执行其它代码
例子:有时限
@Slf4j(topic = "c.TestInterCS")
public class TestInterCS {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.debug("t1尝试获取锁");
try {
if (!lock.tryLock(2, TimeUnit.SECONDS)){
log.debug("t1获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("t1获取到锁");
}finally {
}
},"t1");
Thread t2 = new Thread(()->{
log.debug("t2尝试获取锁");
if (!lock.tryLock()){
log.debug("t2获取不到锁");
return;
}
try {
log.debug("获取到锁");
}finally {
lock.unlock();
}
},"t2");
t1.start();
Sleeper.sleep(1);
t2.start();
}
}
结果
00:44:46.180 c.TestInterCS [t1] - t1尝试获取锁
00:44:46.183 c.TestInterCS [t1] - t1获取到锁
00:44:47.183 c.TestInterCS [t2] - t2尝试获取锁
00:44:47.183 c.TestInterCS [t2] - t2获取不到锁
解释
我们给tryLock方法指定时间为2s,但是因为t1线程始终没有释放锁,所以结果lock.tryLock()依然返回false,我们进行逻辑判断,执行其它的代码
哲学家就餐问题
介绍
之前我们介绍过哲学家就餐问题,当时我们用 顺序加锁的方法解决了死锁的问题,但是当时产生了饥饿问题,这次我们用锁超时解决饥饿问题
代码
@Slf4j(topic = "c.Test23")
public class Test23 {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
if(left.tryLock()) {
try {
// 尝试获得右手筷子
if(right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock(); // 释放自己手里的筷子
}
}
}
}
Random random = new Random();
private void eat() {
log.debug("eating...");
Sleeper.sleep(0.5);
}
}
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
结果
.
.
.
00:54:18.866 c.Philosopher [苏格拉底] - eating…
00:54:18.866 c.Philosopher [亚里士多德] - eating…
00:54:19.373 c.Philosopher [柏拉图] - eating…
00:54:19.373 c.Philosopher [阿基米德] - eating…
00:54:19.881 c.Philosopher [苏格拉底] - eating…
00:54:19.881 c.Philosopher [赫拉克利特] - eating…
00:54:20.386 c.Philosopher [苏格拉底] - eating…
00:54:20.386 c.Philosopher [亚里士多德] - eating…
00:54:20.894 c.Philosopher [亚里士多德] - eating…
00:54:20.894 c.Philosopher [苏格拉底] - eating…
00:54:21.401 c.Philosopher [赫拉克利特] - eating…
00:54:21.401 c.Philosopher [柏拉图] - eating…
00:54:21.908 c.Philosopher [苏格拉底] - eating…
00:54:21.908 c.Philosopher [赫拉克利特] - eating…
00:54:22.417 c.Philosopher [苏格拉底] - eating…
00:54:22.417 c.Philosopher [赫拉克利特] - eating…
.
.
.
.
解释
我们可以看出现在没有产生死锁 且 也没有线程处于饥饿,这里的做法是让Chopstick继承ReentrantLock类,然后Chopstick对象也就是筷子 使用tryLock方法 如果和其他哲学家产生竞争就放弃获取,这样自然而然没有了 由于互相争抢而产生的死锁问题了
06.公平锁
介绍
多个线程按照申请锁的顺序获取锁
源码
可以看出,ReentrantLock有参构造传递的为true则创建出的对象就为公平锁(默认是不公平锁),公平锁一般没有必要 会降低并发度,之后我们学习AQS时会分析它的源码
07.条件变量
介绍
synchronized中也有条件变量,就是我们讲原理时Monitor中的WaitSet,当条件不满足时进入WaitSet中等待。
ReentrantLock的条件变量比synchronized强大之处在于 它是之处多个条件变量的 这就好比 synchronized是那些不满足条件的线程都在一间休息室等消息,而ReentrantLock支持多间休息室 有专门等烟条件的休息室 有专门等待早餐条件的休息室 唤醒时也是按照休息室来唤醒
使用流程
1.await前需要获取锁
2.await执行后 会释放锁 进入conditionObject等待
3.await的线程被唤醒(或打断 或超时)去重新竞争lock锁
4.竞争lock锁成功后 从await后继续执行
例子
还是之前那个wait/notify的例子,这次使用RenntrantLock实现
@Slf4j(topic = "c.Test24")
public class Test24 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
// 等待烟的休息室
static Condition waitCigaretteSet = ROOM.newCondition();
// 等外卖的休息室
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小南").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小女").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasTakeout = true;
waitTakeoutSet.signal();
} finally {
ROOM.unlock();
}
}, "送外卖的").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasCigarette = true;
waitCigaretteSet.signal();
} finally {
ROOM.unlock();
}
}, "送烟的").start();
}
}
结果
01:20:49.292 c.Test24 [小南] - 有烟没?[false]
01:20:49.295 c.Test24 [小南] - 没烟,先歇会!
01:20:49.295 c.Test24 [小女] - 外卖送到没?[false]
01:20:49.295 c.Test24 [小女] - 没外卖,先歇会!
01:20:50.300 c.Test24 [小女] - 可以开始干活了
01:20:51.309 c.Test24 [小南] - 可以开始干活了
解释
我们创建了两个休息室,小南在没有获得烟这个条件前 进入的是waitCigaretteSet休息室,小女在没有获得外卖这个条件前 进入的是waitTakeoutSet这个休息室,相比于之前使用synchronized时小女和小南没有获得条件之前进入的都是WaitSet,这里更加细分了。后面唤醒时 也是按照休息区来唤醒。
边栏推荐
- Sui of the public chain (New Public chain project established by former Facebook /meta employees)
- What is weak network testing? Why should weak network test be carried out? How to conduct weak network test? "Suggested collection"
- (干货)结合Scikit-learn介绍几种常用的特征选择方法
- 关于:在企业局域网中启用 Delivery Optimization
- (CVPR-2022)BiCnet
- What are offline data and real-time data
- 【pm2】pm2常用命令
- 【C语言】程序环境和预处理
- Codeforces Round #805-#808【部分题解】
- Basic process of process scheduling
猜你喜欢

Codeforces Round #809 (Div. 2)【VP记录】

Alibaba最新神作!耗时187天肝出来1015页分布式全栈手册太香了

虹科干货 | 教您如何解析MODBUS中的浮点型数据

ACM mm 2022 oral | dig: the new framework of self-monitoring character recognition refreshes the recognition performance of 11 public scene character data sets, with an average improvement of 5%

时代潮头,华为将风帆对准数字金融的风与海

看完这篇,彻底搞懂 gRPC!

UPC 2022 summer personal training game 12 (number of combinations b)

Data link layer -------- Ethernet and ARP

ES6其他语法及扩展语法总结

Understand chisel language. 21. Chisel sequential circuit (I) -- detailed explanation of chisel register
随机推荐
.net core implements background tasks (scheduled tasks) longbow Tasks component (III)
测试如何应对新的开发模式?
AtCoder Regular Contest 144【VP记录】
四旋翼飞行器1——结构和控制原理
三维点云课程(七)——特征点描述
(dry goods) introduce several common feature selection methods combined with scikit learn
.Net CLR R2R编译的原理简析
简历上写的电商,那请问Redis 如何实现库存扣减操作和防止被超卖?
R语言使用ggpubr包的ggarrange函数将多幅图像组合起来、使用ggexport函数将可视化图像保存为bmp格式(width参数指定宽度、height参数指定高度、res参数指定分辨率)
impala的详细写入流程
【pm2】pm2常用命令
As a background developer, you must know two kinds of filters
小鱼送激光雷达啦 | 恰饭即抽奖第一期
Type-C Bluetooth speaker single C-Port rechargeable OTG solution
Weights & Biases (一)
ES6其他语法及扩展语法总结
行业分析| 物流对讲
【C语言】程序环境和预处理
【luogu P6656】【LOJ 173】【模板】Runs(字符串)(Lyndon 串)
[shutter -- layout] flexible layout (flex and expanded)