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 - 等到了它的烟