相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与 synchronized 一样,都支持可重入
基本语法// 获取锁reentrantLock.lock();try{// 临界区} finally{// 释放锁reentrantLock.unlock();}
1、可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
@Slf4j(topic = "c.TestReentrant")
public class TestReentrant {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();}}
}
由结果可以证明:ReentrantLock 是可重入的
2、可打断
举例说明:
@Slf4j(topic = "c.TestInterrupt")
public class TestInterrupt {public static void main(String[] args) {test1();}private static void test1() {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();}}
}
由结果可以看到,main线程获取到了锁,t1线程尝试去获取锁,然后main线程打断t1线程尝试获取锁的过程
注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
@Slf4j(topic = "c.TestInterrupt")
public class TestInterrupt {public static void main(String[] args) {test2();}private static void test2() {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();}}
}
可以看到t1被主线程打断后,并没异常抛出错误,而是正常执行
3、锁超时
1) 立即失败的场景
public class TestTimeout {public static void main(String[] args) {test2();}private static void test2() {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();}}
}
main线程首先获取到了锁,t1线程尝试去获取锁的时候就会立即失败
2)超时失败
public class TestTimeout {public static void main(String[] args) {test1();}private static void test1() {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();}}
}
由结果可以判断t1线程在尝试获取锁时,阻塞了一秒钟,一秒后还是没有获取到锁,则返回false
4、公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁,ReentrantLock 默认是不公平的
demo演示证明:ReentrantLock 默认是不公平的
public class TestFair {public static void main(String[] args) throws InterruptedException {ReentrantLock lock = new ReentrantLock(false);lock.lock();for (int i = 0; i < 20; i++) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + " running...");} finally {lock.unlock();}}, "t" + i).start();}// 1s 之后去争抢锁Thread.sleep(1000);for (int i = 0; i < 5; i++) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + " running...");} finally {lock.unlock();}}, "强行插入").start();}lock.unlock();}
}
按道理说,500个线程是先启动的,应该先获取到锁,但是实际情况,强制插入线程中途就获取到锁了
5、条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
- await 前需要获得锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.TestCondition")
public class TestCondition {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();log.debug("准备工作,看看有没有烟: {}", hasCigrette);while (!hasCigrette) {log.debug("准备工作,看看有没有烟: {}", hasCigrette);try {// 释放锁,并在该条件变量上等待waitCigaretteQueue.await(); } catch (InterruptedException e) {e.printStackTrace();}}log.debug("等到了它的烟");} finally {lock.unlock();}}).start();new Thread(() -> {try {lock.lock();log.debug("准备工作,看看有没有早餐: {}", hasBreakfast);while (!hasBreakfast) {log.debug("准备工作,看看有没有早餐: {}", 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();}}
}