8.ReentrantLock
目录
ReentrantLock
简介
相对于 synchronized
它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
==与 synchronized 一样,都支持可重入==
基础语法:
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
@Slf4j(topic = "c.ReentrantLockTest")
public class ReentrantLockTest {
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();
}
}
}
输出:
15:39:20.988 c.ReentrantLockTest [main] - execute method1
15:39:20.990 c.ReentrantLockTest [main] - execute method2
15:39:20.990 c.ReentrantLockTest [main] - execute method3
Process finished with exit code 0
可打断 lock.lockInterruptibly()
@Slf4j(topic = "c.LockInterruptiblyTest")
public class LockInterruptiblyTest {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
//没有竞争就会获取锁
//有竞争就进入阻塞队列等待,但可以被打断
lock.lockInterruptibly();
//lock.lock(); //不可打断
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
SleepUtil.sleep(1);
log.debug("执行打断");
t1.interrupt();
} finally {
lock.unlock();
}
}
}
输出:
15:40:30.630 c.LockInterruptiblyTest [main] - 获得了锁
15:40:30.633 c.LockInterruptiblyTest [t1] - 启动...
15:40:31.639 c.LockInterruptiblyTest [main] - 执行打断
15:40:31.640 c.LockInterruptiblyTest [t1] - 等锁的过程中被打断
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 cn.com.wuhm.juc.three.n4.LockInterruptiblyTest.lambda$main$0(LockInterruptiblyTest.java:24)
at java.lang.Thread.run(Thread.java:750)
Process finished with exit code 0
如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
@Slf4j(topic = "c.LockInterruptiblyTest")
public class LockInterruptiblyTest {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("启动...");
// try {
// //没有竞争就会获取锁
// //有竞争就进入阻塞队列等待,但可以被打断
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// e.printStackTrace();
// log.debug("等锁的过程中被打断");
// return;
// }
//不可打断
lock.lock();
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
SleepUtil.sleep(1);
log.debug("执行打断");
t1.interrupt();
} finally {
lock.unlock();
}
}
}
输出:
15:41:41.069 c.LockInterruptiblyTest [main] - 获得了锁
15:41:41.071 c.LockInterruptiblyTest [t1] - 启动...
15:41:42.073 c.LockInterruptiblyTest [main] - 执行打断
15:41:42.073 c.LockInterruptiblyTest [t1] - 获得了锁
Process finished with exit code 0
锁(可设置)超时 lock.tryLock()
立刻返回结果:
@Slf4j(topic = "c.TestTryLock")
public class TestTryLock {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("t1开始尝试获取锁");
if(!lock.tryLock()){
log.debug("未获取到锁");
return;
}
try{
log.debug("获取到锁");
}finally {
lock.unlock();
}
}, "t1");
log.debug("获取锁");
lock.lock();
t1.start();
SleepUtil.sleep(1);
lock.unlock();
}
}
输出:
17:39:53.222 c.TestTryLock [main] - 获取锁
17:39:53.224 c.TestTryLock [t1] - t1开始尝试获取锁
17:39:53.224 c.TestTryLock [t1] - 未获取到锁
Process finished with exit code 0
尝试等待多少时间后返回结果:
@Slf4j(topic = "c.TestTryLock")
public class TestTryLock {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("t1开始尝试获取锁");
try {
if(!lock.tryLock(2, TimeUnit.SECONDS)){
log.debug("未获取到锁");
return;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try{
log.debug("获取到锁");
}finally {
lock.unlock();
}
}, "t1");
log.debug("获取锁");
lock.lock();
t1.start();
SleepUtil.sleep(1);
lock.unlock();
}
}
输出:
17:38:27.472 c.TestTryLock [main] - 获取锁
17:38:27.474 c.TestTryLock [t1] - t1开始尝试获取锁
17:38:28.478 c.TestTryLock [t1] - 获取到锁
Process finished with exit code 0
使用 ReentrantLock 的 tryLock() 解决哲学家就餐问题
代码改造:
筷子类继承 ReentrantLock
public class Chopstick extends ReentrantLock {
private String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
哲学家类 run方法改造
@Override
public void run() {
while (true){
// 使用 ReentrantLock 改造
if(left.tryLock()){
try{
if(right.tryLock()){
try {
eat();
}finally {
right.unlock();
}
}
}finally {
left.unlock();
}
}
}
}
测试结果:
。。。。。。
17:44:42.255 c.Philosopher [苏格拉底] - eating......
17:44:42.255 c.Philosopher [亚里士多德] - eating......
17:44:43.259 c.Philosopher [苏格拉底] - eating......
17:44:43.259 c.Philosopher [亚里士多德] - eating......
17:44:44.263 c.Philosopher [亚里士多德] - eating......
17:44:44.263 c.Philosopher [苏格拉底] - eating......
17:44:45.267 c.Philosopher [亚里士多德] - eating......
17:44:45.267 c.Philosopher [阿基米德] - eating......
17:44:46.272 c.Philosopher [阿基米德] - eating......
17:44:46.272 c.Philosopher [亚里士多德] - eating......
17:44:47.276 c.Philosopher [亚里士多德] - eating......
17:44:47.276 c.Philosopher [苏格拉底] - eating......
。。。。。。
不再产生死锁和饥饿的问题
(多个)条件变量
synchronized也有条件变量,synchronized是操作系统层面实现的,而ReentrantLock是java层面实现的,但是其实现 的逻辑和思路大致相同,操作系统的Monitor
由,waitSet
,EntryList
,Owner
,三部分组成,其中waitSet
等于ReentrantLock的条件变量。ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
await
前需要获得锁await
执行后,会释放锁,进入conditionObject
等待await
的线程被唤醒(或打断、或超时)去重新竞争 lock 锁- 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.Test10")
public class Test10 {
static ReentrantLock lock = new ReentrantLock();
// 定义两个条件变量
static Condition smokeWaitSetCondition = lock.newCondition();
static Condition takeoutWaitSetCondition = lock.newCondition();
static volatile boolean hasSmoke = false;
static volatile boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(()->{
lock.lock();
try {
while (!hasSmoke) {
log.debug("没有烟,休息一会");
try {
smokeWaitSetCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("来了一口烟,神清气爽,继续工作");
}finally {
lock.unlock();
}
}, "小明").start();
new Thread(()->{
lock.lock();
try {
while (!hasSmoke) {
log.debug("外卖还没到,休息一会");
try {
takeoutWaitSetCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("来了一口饭,横扫饥饿,继续工作");
}finally {
lock.unlock();
}
}, "小强").start();
SleepUtil.sleep(1);
sendSmoke();
sendTakeout();
}
private static void sendSmoke(){
lock.lock();
try{
log.debug("送烟来了");
hasSmoke = true;
smokeWaitSetCondition.signal();
}finally {
lock.unlock();
}
}
private static void sendTakeout(){
lock.lock();
try{
log.debug("外卖来了");
hasTakeout = true;
takeoutWaitSetCondition.signal();
}finally {
lock.unlock();
}
}
}
输出:
14:26:50.387 c.Test10 [小明] - 没有烟,休息一会
14:26:50.389 c.Test10 [小强] - 外卖还没到,休息一会
14:26:51.391 c.Test10 [main] - 送烟来了
14:26:51.391 c.Test10 [main] - 外卖来了
14:26:51.391 c.Test10 [小明] - 来了一口烟,神清气爽,继续工作
14:26:51.391 c.Test10 [小强] - 来了一口饭,横扫饥饿,继续工作
Process finished with exit code 0
运用案例
控制线程执行顺序
需要控制线程2总是比线程1先执行
方式一:wait & notify
@Slf4j(topic = "c.SequenceControl1")
public class SequenceControl1 {
static Object lock = new Object();
static boolean running = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock){
while (!running){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("t1 start execute");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock){
log.debug("t2 start execute");
running = true;
lock.notifyAll();
}
}, "t2");
SleepUtil.sleep(0.1);
t1.start();
t2.start();
}
}
局限性:
- 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了运行标记来判断该不该wait
- 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决此问题
- 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为同步对象上的等待线程可能不止一个
方式二:park & unpark (推荐)
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。
并且是以线程为单位进行『暂停』和『恢复』,不需要『同步对象』和『运行标记』
@Slf4j(topic = "c.SequenceControl2")
public class SequenceControl2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
log.debug("t1 start execute");
}, "t1");
Thread t2 = new Thread(() -> {
log.debug("t2 start execute");
LockSupport.unpark(t1);
}, "t2");
SleepUtil.sleep(0.1);
t1.start();
t2.start();
}
}
输出:
15:05:47.893 c.SequenceControl2 [t2] - t2 start execute
15:05:47.895 c.SequenceControl2 [t1] - t1 start execute
Process finished with exit code 0
==注意使用park和unpark与输出语句的先后顺序==
交替打印
场景:线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
方式一:wait & notifyAll
解析:之前控制顺序是通过标志位,现在是多个线程顺序控制,我们可以通过1,2,3……这样的数值型表示线程执行的循序
@Slf4j(topic = "c.AlternatePrint1")
public class AlternatePrint1 {
public static void main(String[] args) {
SyncWaitNotify syncWaitNotify = new SyncWaitNotify(1, 5);
new Thread(()->{
syncWaitNotify.print(1, 2, "a");
}, "t1").start();
new Thread(()->{
syncWaitNotify.print(2, 3, "b");
}, "t2").start();
new Thread(()->{
syncWaitNotify.print(3, 1, "c");
}, "t3").start();
}
}
class SyncWaitNotify{
/**
* 线程的标记:线程1-1;线程2-2;线程3-3
*/
private int flag;
/**
* 循环次数
*/
private int loopNumber;
public SyncWaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
public void print(int currentFlag, int nextFlag, String ch){
for (int i = 0; i < loopNumber; i++) {
synchronized (this){
// 如果当前标记不是这个线程的,进入阻塞队列
while (this.flag != currentFlag){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 如果相等输出字符
System.out.print(ch);
// 将标记为移向下一个线程标志位
this.flag = nextFlag;
// 唤醒阻塞队列所有线程
this.notifyAll();
}
}
}
}
输出:
abcabcabcabcabc
方式二:ReentrantLock
@Slf4j(topic = "c.AlternatePrint2")
public class AlternatePrint2 {
public static void main(String[] args) {
SyncAWaitSignal syncAWaitSignal = new SyncAWaitSignal(5);
Condition conditionA = syncAWaitSignal.newCondition();
Condition conditionB = syncAWaitSignal.newCondition();
Condition conditionC = syncAWaitSignal.newCondition();
new Thread(()->{
syncAWaitSignal.print(conditionA, conditionB, "a");
}, "t1").start();
new Thread(()->{
syncAWaitSignal.print(conditionB, conditionC, "b");
}, "t2").start();
new Thread(()->{
syncAWaitSignal.print(conditionC, conditionA, "c");
}, "t3").start();
// 主线程需要主动唤醒第一个线程工作
SleepUtil.sleep(0.2);
syncAWaitSignal.lock();
try{
conditionA.signal();
}finally {
syncAWaitSignal.unlock();
}
}
}
class SyncAWaitSignal extends ReentrantLock {
/**
* 循环次数
*/
private int loopNumber;
public SyncAWaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(Condition currentCondition, Condition nextCondition, String ch){
for (int i = 0; i < loopNumber; i++) {
// 刚开始让所有线程进入阻塞状态
this.lock();
try{
try {
currentCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.print(ch);
// 唤醒下一个线程
nextCondition.signalAll();
}finally {
this.unlock();
}
}
}
}
输出:
abcabcabcabcabc
方式三:park & unpark
@Slf4j(topic = "c.AlternatePrint3")
public class AlternatePrint3 {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
SyncParkUnPark syncParkUnPark = new SyncParkUnPark(5);
t1 = new Thread(() -> {
syncParkUnPark.print(t2, "a");
}, "t1");
t2 = new Thread(()->{
syncParkUnPark.print(t3, "b");
}, "t2");
t3 = new Thread(()->{
syncParkUnPark.print(t1, "c");
}, "t3");
t1.start();
t2.start();
t3.start();
// 主线程需要主动唤醒第一个线程工作
SleepUtil.sleep(0.2);
LockSupport.unpark(t1);
}
}
class SyncParkUnPark extends ReentrantLock {
/**
* 循环次数
*/
private int loopNumber;
public SyncParkUnPark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(Thread nextThread, String ch){
for (int i = 0; i < loopNumber; i++) {
// 刚开始让所有线程进入阻塞状态
LockSupport.park();
System.out.print(ch);
LockSupport.unpark(nextThread);
}
}
}
输出:
abcabcabcabcabc