当前位置:网站首页>并发之ReentrantLock
并发之ReentrantLock
2022-08-03 07:48:00 【七国的天下,我要九十九】
与synchronized 具备以下特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与 synchronized相同点,都支持可重入.
1 基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
2 可重入
指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住.
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
/* 运行结果: 17:59:11.862 [main] c.TestReentrant - execute method1 17:59:11.865 [main] c.TestReentrant - execute method2 17:59:11.865 [main] c.TestReentrant - execute method3 */
3 可打断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("执行打断");
} finally {
lock.unlock();
}
/* 运行结果: 18:02:40.520 [main] c.TestInterrupt - 获得了锁 18:02:40.524 [t1] c.TestInterrupt - 启动... 18:02:41.530 [main] c.TestInterrupt - 执行打断 java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchr onizer.java:898) */
不可中断模式
使用了 interrupt 也不会让等待中断.
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
lock.lock();
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("执行打断");
sleep(1);
} finally {
log.debug("释放了锁");
lock.unlock();
}
/* 运行结果: 18:06:56.261 [main] c.TestInterrupt - 获得了锁 18:06:56.265 [t1] c.TestInterrupt - 启动... 18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁 18:06:58.267 [main] c.TestInterrupt - 释放了锁 18:06:58.267 [t1] c.TestInterrupt - 获得了锁 */
4 锁超时
立刻失败
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
if (!lock.tryLock()) {
log.debug("获取立刻失败,返回");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(2);
} finally {
lock.unlock();
}
/* 运行结果: 18:15:02.918 [main] c.TestTimeout - 获得了锁 18:15:02.921 [t1] c.TestTimeout - 启动... 18:15:02.921 [t1] c.TestTimeout - 获取立刻失败,返回 */
超时失败
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("获取等待 1s 后失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(2);
} finally {
lock.unlock();
}
/* 运行结果: 18:19:40.537 [main] c.TestTimeout - 获得了锁 18:19:40.544 [t1] c.TestTimeout - 启动... 18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回 */
使用tryLock解决哲学家就餐问题
筷子类
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
哲学家类
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();
}
}
}
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
}
5 公平锁
ReentrantLock 默认是不公平的.
ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start...");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入").start();
lock.unlock();
/* 运行结果: t42 running... t43 running... 强行插入 start... 强行插入 running... t44 running... t45 running... t46 running... */
强行插入,有机会在中间输出(复现比较困难)
公平锁
创建对象时,将对象设置为true.
ReentrantLock lock = new ReentrantLock(true);
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start...");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入").start();
lock.unlock();
/* 运行结果: t485 running... t481 running... 强行插入 running... */
保证每次执行,且总在最后输出.公平锁一般没有必要,并且会降低并发度.
6 条件变量
synchronized 中也有条件变量,ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的.
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤 醒
说明:
- await 前需要获得锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
new Thread(() -> {
try {
lock.lock();
while (!hasCigrette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitbreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
sleep(1);
sendBreakfast();
sleep(1);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
log.debug("送烟来了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
log.debug("送早餐来了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}
/* 运行结果: 18:52:27.680 [main] c.TestCondition - 送早餐来了 18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐 18:52:28.683 [main] c.TestCondition - 送烟来了 18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟 */
边栏推荐
猜你喜欢
Poke the myth of Web3?Poke the iron plate.
【云原生--Kubernetes】Pod重启策略
Golang协程goroutine的调度与状态变迁分析
图解Kernel Device Tree(设备树)的使用
[Kaggle combat] Prediction of the number of survivors of the Titanic (from zero to submission to Kaggle to model saving and restoration)
品牌方发行NFT时,应如何考量实用性?
Charles抓包工具学习记录
vs 2022无法安装 vc_runtimeMinmum_x86错误
Transformer、BERT、GPT 论文精读笔记
AI mid-stage sequence labeling task: three data set construction process records
随机推荐
跨域嵌套传递信息(iframe)
《剑指Offer》刷题之打印从1到最大的n位数
Haisi project summary
Windows安装MySQL(MIS)
面试介绍项目经验(转)
- display image API OpenCV 】 【 imshow () to a depth (data type) at different image processing methods
mysql备份时的快照原理
【第1天】SQL快速入门-基础查询(SQL 小虚竹)
使用pipreqs导出项目所需的requirements.txt(而非整个环境)
“碳中和”愿景下,什么样的数据中心才是我们需要的?
加速FinOps实践,为企业降本增效
Taro框架-微信小程序-内嵌h5页面
ViewModel 记录下 +
volta管理node版本
vim 折叠函数
rust 学习笔记
Roson的Qt之旅#103 QML之标签导航控件TabBar
审批流设计
PostMan使用,访问路径@RequestMapping
循环神经网络RNN基础《PyTorch深度学习实践》