一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情。
活锁
区别于死锁,活锁是出现在两个线程互相改变对方的结束条件,最后谁也无法结束 例如
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}
饥饿
很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
死锁图解
顺序加锁解决方案
即获取锁不发生循环
ReentrantLock(可重入锁)
相对于 synchronized 它具备如下特点
-
可中断
-
可以设置超时时间
-
可以设置为公平锁
-
支持多个条件变量
基本语法:
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
可打断
锁超时
通过teentrantLock解决哲学家就餐问题
公平锁
ReentrantLock 默认是不公平的
公平:先到先得
不公平:随机
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
-
synchronized 是那些不满足条件的线程都在一间休息室等消息
-
而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
-
await 前需要获得锁
-
await 执行后,会释放锁,进入 conditionObject 等待
-
await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
-
竞争 lock 锁成功后,从 await 后继续执行
16:33:51.585 c.Test24 [小南] - 有烟没?[false]
16:33:51.589 c.Test24 [小南] - 没烟,先歇会!
16:33:51.589 c.Test24 [小女] - 外卖送到没?[false]
16:33:51.589 c.Test24 [小女] - 没外卖,先歇会!
16:33:52.585 c.Test24 [小女] - 可以开始干活了
16:33:53.589 c.Test24 [小南] - 可以开始干活了