《重新学习多线程》-- ReentrantLock

108 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题

死锁图解

image.png

顺序加锁解决方案

image.png

即获取锁不发生循环

ReentrantLock(可重入锁)

相对于 synchronized 它具备如下特点

  • 可中断

  • 可以设置超时时间

  • 可以设置为公平锁

  • 支持多个条件变量

基本语法:

// 获取锁

reentrantLock.lock();

try {

    // 临界区

} finally {

    // 释放锁
    reentrantLock.unlock();

}

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁

如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

carbon (20).png

可打断

carbon (21).png

锁超时

carbon (22).png

通过teentrantLock解决哲学家就餐问题

carbon (23).png

公平锁

ReentrantLock 默认是不公平的
公平:先到先得
不公平:随机

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  • synchronized 是那些不满足条件的线程都在一间休息室等消息

  • 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

使用要点:

  • await 前需要获得锁

  • await 执行后,会释放锁,进入 conditionObject 等待

  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁

  • 竞争 lock 锁成功后,从 await 后继续执行

carbon (25).png

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 [小南] - 可以开始干活了