ReentrantLock

文章详细介绍了Java中的ReentrantLock锁的特性,包括它的可重入性,支持线程的可中断,可以设置超时时间获取锁,以及相比synchronized更强大的多条件变量支持。通过示例代码展示了如何使用这些特性,如在等待锁过程中可以被中断,以及设置超时时间避免无限期等待。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相对于 synchronized 它具备如下特点
  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与 synchronized 一样,都支持可重入 基本语法

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

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

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
可打断

示例

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 - 获得了锁
锁超时

立刻失败

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 后失败,返回

条件变量

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

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

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

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

  • 使用要点:

    • await 前需要获得锁
    • await 执行后,会释放锁,进入 conditionObject 等待
    • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
    • 竞争 lock 锁成功后,从 await 后继续执行

例子:

@Slf4j(topic = "c.Test24")
public class Test24 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM = new ReentrantLock();
    // 等待烟的休息室
    static Condition waitCigaretteSet = ROOM.newCondition();
    // 等外卖的休息室
    static Condition waitTakeoutSet = ROOM.newCondition();
    public static void main(String[] args) throws InterruptedException {

        new Thread(()->{
            ROOM.lock();
            try{
                log.debug("有烟没?[{}]", hasCigarette);
                while(!hasCigarette){
                    log.debug("没烟,先歇会!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            }finally {
                ROOM.unlock();
            }
        },"小南").start();

        new Thread(()->{
            ROOM.lock();
            try{
                log.debug("外卖送到没?[{}]", hasTakeout);
                while(!hasTakeout){
                    log.debug("没外卖,先歇会!");
                    try {
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            }finally {
                ROOM.unlock();
            }
        },"小女").start();

        sleep(1);
        new Thread(()->{
            ROOM.lock();
            try{
                hasCigarette = true;
                waitCigaretteSet.signal();
            }finally {
                ROOM.unlock();
            }
        },"送烟的").start();

        new Thread(()->{
            ROOM.lock();
            try{
                hasTakeout = true;
                waitTakeoutSet.signal();
            }finally {
                ROOM.unlock();
            }
        },"送外卖的").start();
    }
}

输出

20:48:03.710 [小南] DEBUG c.Test24 - 有烟没?[false]
20:48:03.714 [小南] DEBUG c.Test24 - 没烟,先歇会!
20:48:03.714 [小女] DEBUG c.Test24 - 外卖送到没?[false]
20:48:03.714 [小女] DEBUG c.Test24 - 没外卖,先歇会!
20:48:03.714 [小南] DEBUG c.Test24 - 可以开始干活了
20:48:03.714 [小女] DEBUG c.Test24 - 可以开始干活了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值