ReentrantLock 使用

1、介绍

相对于 synchronized 它具备如下特点:

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

与 synchronized 一样,都支持可重入。

基本语法

// 获取锁
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();
        }
    }

输出:

09:52:08.042 [main] DEBUG c.Test1 - execute method1
09:52:08.045 [main] DEBUG c.Test1 - execute method2
09:52:08.045 [main] DEBUG c.Test1 - execute method3

3、可打断

    ReentrantLock lock = new ReentrantLock();

    Thread t1 = new Thread(() -> {
        log.debug("启动...");
        try {
            //如果没有竞争那么此方法就会获取lock对象锁,
            //如果有竞争就进入阻塞队列,可以被其他线程用interrupt方法打断
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.debug("等锁的过程中被打断");
            return;
        }
        try {
            log.debug("获得了锁");
        } finally {
            lock.unlock();
        }
    }, "t1");

    lock.lock(); //主线程首先获得锁
    log.debug("获得了锁");
    t1.start(); //t1线程进入阻塞队列
    try {
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt(); //t1线程被打断,响应中断
        log.debug("执行打断");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
    

输出 

09:55:28.092 [main] DEBUG c.Test2 - 获得了锁
09:55:28.094 [t1] DEBUG c.Test2 - 启动...
09:55:29.095 [main] DEBUG c.Test2 - 执行打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at p4_13.Test2.lambda$main$0(Test2.java:22)
	at java.lang.Thread.run(Thread.java:748)
09:55:29.096 [t1] DEBUG c.Test2 - 等锁的过程中被打断

注意如果是不可中断模式,那么即使使用了 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(); //t1线程进入阻塞队列
    try {
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
        log.debug("执行打断");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }

输出

10:02:06.285 [main] DEBUG c.Test2 - 获得了锁
10:02:06.288 [t1] DEBUG c.Test2 - 启动...
10:02:07.290 [main] DEBUG c.Test2 - 执行打断
10:02:07.290 [t1] DEBUG c.Test2 - 获取了锁

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 {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }

输出

10:07:14.085 [main] DEBUG c.Test3 - 获得了锁
10:07:14.089 [t1] DEBUG c.Test3 - 启动...
10:07:14.089 [t1] DEBUG c.Test3 - 获取立刻失败,返回

 超时失败

    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 {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }

5、条件变量

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) throws InterruptedException {
        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();
        
        TimeUnit.SECONDS.sleep(1);
        sendBreakfast();
        TimeUnit.SECONDS.sleep(1);
        sendCigarette();
    }

    public static void sendCigarette(){
        lock.lock();
        try {
            log.debug("发烟了...");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        }finally {
            lock.unlock();
        }
    }

    public static void sendBreakfast(){
        lock.lock();
        try {
            log.debug("早餐来了...");
            hasBreakfast = true;
            waitbreakfastQueue.signal();
        }finally {
            lock.unlock();
        }
    }

输出:

10:21:18.175 [main] DEBUG c.Test6 - 早餐来了...
10:21:18.183 [Thread-1] DEBUG c.Test6 - 等到了它的早餐
10:21:19.186 [main] DEBUG c.Test6 - 发烟了...
10:21:19.186 [Thread-0] DEBUG c.Test6 - 等到了它的烟
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鲁蛋儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值