ReentrantLock
概览
相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量(相当于有多个WaitSet)
- 与 synchronized 一样,都支持可重入
基本语法
java
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
特性
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
public void lock()
:获取锁public void unlock()
:释放锁
java
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
logger.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
logger.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
logger.debug("execute method3");
} finally {
lock.unlock();
}
}
// 输出
2023-09-27 10:15:07.470 [main] DEBUG Main(:) - execute method1
2023-09-27 10:15:07.473 [main] DEBUG Main(:) - execute method2
2023-09-27 10:15:07.473 [main] DEBUG Main(:) - execute method3
可打断
lock()
是不可中断模式,如果需要被其他线程打断可以使用lockInterruptibly()
。
public void lockInterruptibly() throws InterruptedException
:获取可打断锁public void interrupt()
:中断线程
java
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
logger.debug("启动...");
try {
// 如果没有竞争,那么此方法会获得 lock 对象锁
// 如果有竞争就会进入阻塞状态,可以被其他线程打断
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
logger.debug("等锁的过程中被打断");
return;
}
try {
logger.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
// 主线程加锁
lock.lock();
logger.debug("获得了锁");
t1.start();
try {
sleep(1);
// 打断
t1.interrupt();
logger.debug("执行打断");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// 输出
2023-09-27 10:22:05.984 [main] DEBUG Main(:) - 获得了锁
2023-09-27 10:22:05.987 [t1] DEBUG Main(:) - 启动...
2023-09-27 10:22:05.988 [main] DEBUG Main(:) - 执行打断
2023-09-27 10:22:05.988 [t1] DEBUG Main(:) - 等锁的过程中被打断
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 thread.Main.lambda$main$0(Main.java:22)
at java.lang.Thread.run(Thread.java:748)
锁超时
可打断是一种被动的打断方式,ReentrantLock
提供了一种主动的打断方式:锁超时。
public boolean tryLock()
:尝试获取锁,获取不到锁,放弃等待(阻塞)public boolean tryLock(long timeout, TimeUnit unit)
:在特定时间内尝试获取锁,获取不到锁,放弃等待(阻塞)
java
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
logger.debug("尝试获取锁...");
try {
// 1秒钟内尝试获取锁
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
logger.debug("获取不到锁,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
logger.debug("获取不到锁,返回");
throw new RuntimeException(e);
}
try {
logger.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
// 主线程 先获取锁
lock.lock();
logger.debug("获得了锁");
t1.start();
try {
sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// 输出
2023-09-27 10:32:56.614 [main] DEBUG Main(:) - 获得了锁
2023-09-27 10:32:56.618 [t1] DEBUG Main(:) - 尝试获取锁...
2023-09-27 10:32:57.125 [t1] DEBUG Main(:) - 获得了锁
公平锁
synchronized锁中,在entrylist等待的锁在竞争时不是按照先到先得来获取锁的,所以说synchronized锁时不公平的;
ReentranLock锁默认是不公平的,但是可以通过设置实现公平锁。本意是为了解决之前提到的饥饿问题,但是公平锁一般没有必要,会降低并发度,使用trylock也可以实现。
public ReentrantLock(boolean fair)
:默认不公平锁,可以通过配置设置是否使用公平锁
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待 ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
常用API
public Condition newCondition()
:Condition类可以实现等待/通知模式void await() throws InterruptedException
:等待boolean await(long time, TimeUnit unit) throws InterruptedException
:等待有限时间void signal()
:唤醒特定等待中的线程void signalAll()
:唤醒所有等待中的线程
java
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();
}
}
logger.debug("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitbreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.debug("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
sleep(1000);
sendBreakfast();
sleep(1000);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
logger.debug("送烟来了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
logger.debug("送早餐来了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}
// 输出
2023-09-27 11:36:33.368 [main] DEBUG Main(:) - 送早餐来了
2023-09-27 11:36:33.370 [Thread-2] DEBUG Main(:) - 等到了它的早餐
2023-09-27 11:36:34.372 [main] DEBUG Main(:) - 送烟来了
2023-09-27 11:36:34.372 [Thread-1] DEBUG Main(:) - 等到了它的烟
- await 前需要获得锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,执行唤醒的线程也必须先获得锁
- 竞争 lock 锁成功后,从 await 后继续执行