多线程学习篇七:ReentrantLock
1. ReentrantLock 特征
- 可重入
- 可打断
- 可尝试(在指定时间段)获取锁
- 可设置公平(非公平)锁
- 支持多个条件变量
2. 案例演示
2.1 可重入
@Slf4j(topic = "c.Test01")
public class Test01 {private static final ReentrantLock LOCK = new ReentrantLock();public static void m1() {LOCK.lock();try {log.info("execute m1");m2();} finally {LOCK.unlock();}}public static void m2() {LOCK.lock();try {log.info("execute m2");m3();} finally {LOCK.unlock();}}public static void m3() {LOCK.lock();try {log.info("execute m3");} finally {LOCK.unlock();}}public static void main(String[] args) {m1();}
}
通过运行结果得出结论:main 线程在执行 m1 方法的时候获取到锁,在执行 m2、m3 方法时同样获取到了锁,所以 ReentrantLock 是可重入的
2.2 可打断
2.2.1 使用 lockInterruptibly 的方式获取锁
@Slf4j(topic = "c.Test02")
public class Test02 {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(() -> {try {log.info("lock interruptibly");lock.lockInterruptibly();} catch (InterruptedException e) {log.info(Thread.currentThread().getName() + "线程被打断");return;}try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "t1");lock.lock();log.info(Thread.currentThread().getName() + " 获取到锁");t1.start();try {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info(Thread.currentThread().getName() + " 执行打断");t1.interrupt();} finally {lock.unlock();}}
}
通过运行结果,得出结论:
- main 线程获取到锁
- t1 线程尝试获取锁,但是现在锁的拥有者是 main,所以 t1 线程进入阻塞状态
- main 线程执行 interrupt 方法
- t1 线程被打断
2.2.2 将 lockInterruptibly 方法替换成 lock
@Slf4j(topic = "c.Test03")
public class Test03 {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(() -> {log.info("lock");lock.lock();try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "t1");lock.lock();log.info(Thread.currentThread().getName() + " 获取到锁");t1.start();try {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info(Thread.currentThread().getName() + " 执行打断");t1.interrupt();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}} finally {lock.unlock();}}
}
通过运行结果,得出结论:
- main 线程获取到锁
- t1 线程尝试获取锁,但是现在锁的拥有者是 main,所以 t1 线程进入阻塞状态
- main 线程执行 interrupt 方法,t1 线程仍处于阻塞状态,并没有被打断
- 2 秒后 main 线程释放锁,这时候 t1 线程被唤醒,继续向下执行
2.3 可尝试(在指定时间段)获取锁
2.3.1 尝试获取锁,未获取到锁,直接返回
@Slf4j(topic = "c.Test04")
public class Test04 {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(() -> {if (!lock.tryLock()) {log.info(Thread.currentThread().getName() + " 未获取到锁");return;}try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "t1");lock.lock();log.info(Thread.currentThread().getName() + " 获取到锁");t1.start();try {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}} finally {log.info(Thread.currentThread().getName() + " 解锁");lock.unlock();}}
}
通过运行结果,得出结论:
- main 线程获取到锁
- t1 线程尝试获取锁,未获取到锁,直接返回
- 2 秒后 main 线程解锁
2.3.2 在指定时间内,尝试获取锁
@Slf4j(topic = "c.Test05")
public class Test05 {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(() -> {try {log.info(Thread.currentThread().getName() + "线程等待1分钟,尝试获取锁");if (!lock.tryLock(1, TimeUnit.MINUTES)) {log.info(Thread.currentThread().getName() + " 未获取到锁");return;}} catch (InterruptedException e) {throw new RuntimeException(e);}try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "t1");lock.lock();log.info(Thread.currentThread().getName() + " 获取到锁");t1.start();try {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}} finally {log.info(Thread.currentThread().getName() + " 解锁");lock.unlock();}}
通过运行结果,得出结论:
- main 线程获取到锁
- t1 线程尝试获取锁,未获取到锁,等待1分钟
- 2 秒后 main 线程解锁
- 等待时间在 1 分钟内,t1 线程获取到锁
2.4 可设置公平(非公平)锁
2.4.1 设置非公平锁 (默认情况下,使用的是非公平锁)
@Slf4j(topic = "c.Test06")
public class Test06 {public static void main(String[] args) throws Exception {ReentrantLock lock = new ReentrantLock();log.info(Thread.currentThread().getName() + " 获取到锁");lock.lock();try {List<Thread> list = IntStream.range(0, 500).boxed().map(i ->new Thread(() -> {lock.lock();try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "t" + (i + 1))).collect(Collectors.toList());list.forEach(Thread::start);TimeUnit.MILLISECONDS.sleep(1000);new Thread(() -> {lock.lock();try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "插入线程").start();} finally {lock.unlock();}}
}
通过运行结果,得出结论:
- main 线程获取到锁
- 启动 500 个线程(500个线程获取不到锁,进入阻塞状态)
- main 线程睡眠 1 秒,启动 “插入线程”(确保 “插入线程” 位于阻塞队列末尾)
- main 线程释放锁,默认情况下,ReentrantLock 是非公平锁,所以 “插入线程” 有提前运行的机会
PS:如果不是期望结果,可以运行多次
2.4.2 设置公平锁
@Slf4j(topic = "c.Test06")
public class Test06 {public static void main(String[] args) throws Exception {ReentrantLock lock = new ReentrantLock(true);log.info(Thread.currentThread().getName() + " 获取到锁");lock.lock();try {List<Thread> list = IntStream.range(0, 500).boxed().map(i ->new Thread(() -> {lock.lock();try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "t" + (i + 1))).collect(Collectors.toList());list.forEach(Thread::start);TimeUnit.MILLISECONDS.sleep(1000);new Thread(() -> {lock.lock();try {log.info(Thread.currentThread().getName() + " 获取到锁");} finally {lock.unlock();}}, "插入线程").start();} finally {lock.unlock();}}
}
通过运行结果,得出结论: 每次 “插入线程” 都是最后执行
2.5 支持多个变量
@Slf4j(topic = "c.Test07")
public class Test07 {private static final ReentrantLock LOCK = new ReentrantLock();private static final Condition FIRST_CONDITION = LOCK.newCondition();private static final Condition SECOND_CONDITION = LOCK.newCondition();private static volatile boolean FIRST_SWITCH = false;private static volatile boolean SECOND_SWITCH = false;private static void turnOnFirstSwitch() {LOCK.lock();try {log.info("turn on first switch");FIRST_SWITCH = true;FIRST_CONDITION.signal();} finally {LOCK.unlock();}}private static void turnOnSecondSwitch() {LOCK.lock();try {log.info("turn on first switch");SECOND_SWITCH = true;SECOND_CONDITION.signal();} finally {LOCK.unlock();}}public static void main(String[] args) throws Exception {new Thread(() -> {try {LOCK.lock();log.info(Thread.currentThread().getName() + " 获取到锁");while (!FIRST_SWITCH) {try {FIRST_CONDITION.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}log.info(Thread.currentThread().getName() + " resume");} finally {LOCK.unlock();}}, "t1").start();new Thread(() -> {try {LOCK.lock();log.info(Thread.currentThread().getName() + " 获取到锁");while (!SECOND_SWITCH) {try {SECOND_CONDITION.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}log.info(Thread.currentThread().getName() + " resume");} finally {LOCK.unlock();}}, "t2").start();log.info("main sleep");TimeUnit.SECONDS.sleep(1);turnOnFirstSwitch();log.info("main sleep");TimeUnit.SECONDS.sleep(1);turnOnSecondSwitch();}
}
通过运行结果,得出结论:
- main 线程启动 t1、t2 线程,并进入睡眠
- t1、t2 线程启动后,分别获取到锁,但因为条件不满足,进入阻塞状态
- 1 秒后 main 线程执行 turnOnFirstSwitch 方法,将变量 FIRST_SWITCH 设为 true,并再次睡眠,此时 t1 线程条件满足,继而被唤醒
- main 线程睡眠 1 秒后, main 线程执行 turnOnSecondSwitch 方法,将变量 SECOND_SWITCH 设为 true,t2 线程条件满足,继而被唤醒