13 ReentrantLock

1.相对于 synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

2.于 synchronized 相同点

与 synchronized 一样,都支持可重入

3. 基本语法

// 获取锁
reentrantLock.lock();
try {
        // 临界区
    } finally {
        // 释放锁
        reentrantLock.unlock();
    }

4. 可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

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

5.可打断

示例

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) 
 at 
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchron
izer.java:1222) 
 at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) 
 at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17) 
 at java.lang.Thread.run(Thread.java:748) 
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断

注意如果是不可中断模式,那么即使使用了 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 - 获得了锁

6.锁超时

立刻失败

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);
    }
}

7.公平锁

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();

强行插入,有机会在中间输出
注意:该实验不一定总能复现

t39 running... 
t40 running... 
t41 running... 
t42 running... 
t43 running... 
强行插入 start... 
强行插入 running... 
t44 running... 
t45 running... 
t46 running... 
t47 running... 
t49 running...

改为公平锁后

ReentrantLock lock = new ReentrantLock(true);

强行插入,总是在最后输出

t465 running... 
t464 running... 
t477 running... 
t442 running... 
t468 running... 
t493 running... 
t482 running... 
t485 running... 
t481 running... 
强行插入 running...

公平锁一般没有必要,会降低并发度,后面分析原理时会讲解

8.条件变量

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

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 - 等到了它的烟
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值