1.ReentrantLock可重入锁
1.1.简介
1>.可重入是指同一个线程如果首次获得了这把锁,那么由于它是这把锁的拥有者,因此该线程有权利(/优先)再次获取这把锁
;如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住;
ReentrantLock底层也是基于Monitor对象实现的,只不过它是在Java级别实现的(CAS+AQS),而synchronized是在JVM层面基于Monitor对象实现的线程安全/线程同步的机制,源码是C++的源码;
2>.ReentrantLock相对于Synchronized它具备如下特点:
①.可中断;
②.可以设置超时时间;
③.可以设置为公平锁(/先进先出,不会出现线程饥饿现象);
④.支持多个条件变量;
它与synchronized一样,都支持可重入!
1.2.基本语法
// 获取锁
ReentrantLock reentrantLock = new ReentrantLock(true);
// 加锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁(这一步很重要!!!)
reentrantLock.unlock();
}
Synchronized是在关键字这个级别保护临界区,而ReentrantLock是在对象的级别保护临界区,因此必须要先创建一个ReentrantLock对象;
1.3.可重入案例
@Slf4j
public class TestReentrantLockDemo1 {
//定义ReentrantLock对象
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
//加锁/获取锁,保护临界区
reentrantLock.lock();
try {
log.info("进入mian方法");
//当前线程获取到锁,但是没有释放,再去调用其他同步代码
m1();
} catch (Exception e) {
e.printStackTrace();
} finally {
log.info("main方法释放锁{}",reentrantLock);
//解锁,会唤醒其他等待/阻塞中的线程
//注意这个操作必须要和之前的lock()加锁操作成对出现!!!
reentrantLock.unlock();
}
}
private static void m1() {
//加锁/获取锁,保护临界区
reentrantLock.lock();
try {
log.info("进入m1方法");
m2();
} catch (Exception e) {
e.printStackTrace();
} finally {
log.info("m1方法释放锁{}",reentrantLock);
//解锁,会唤醒其他等待/阻塞中的线程
//注意这个操作必须要和之前的lock()加锁操作成对出现!!!
reentrantLock.unlock();
}
}
private static void m2() {
//加锁/获取锁,保护临界区
reentrantLock.lock();
try {
log.info("进入m2方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
log.info("m2方法释放锁{}",reentrantLock);
//解锁,会唤醒其他等待/阻塞中的线程
//注意这个操作必须要和之前的lock()加锁操作成对出现!!!
reentrantLock.unlock();
}
}
}
1.4.可打断案例
@Slf4j
public class TestReentrantLockDemo2 {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
log.info("线程t1尝试获取锁...");
//加锁并且指定当线程是可以被打断的
//即如果当前没有其他线程竞争锁,那么当前线程正常获取到锁,相当于执行了lock()操作;如果有其他线程竞争锁,而当前当前线程没有抢到锁,那么当前线程就会进入到阻塞队列中等待,但是这个线程的等待状态是可以被其他线程用interrupt()方法打断/终止的!
//可以避免线程无限制等待/阻塞,造成死锁!
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
log.info("没有获得锁,线程t1被打断,运行结束!");
e.printStackTrace();
return;
}
try {
//获取到锁,执行业务代码
log.info("线程t1获取到锁,执行业务代码...");
} finally {
log.info("线程t1释放锁");
//解锁,会唤醒其他等待/阻塞中的线程
reentrantLock.unlock();
}
}, "t1");
//main线程获取锁
reentrantLock.lock();
try {
log.info("main线程获取到锁...");
t1.start();
//main线程获取到锁,睡眠1s,但是没有释放锁,也就是说其他线程无法获取到当前这把锁而进入到阻塞队列中等待
Thread.sleep(1000);
//打断其他线程的等待状态
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.info("main线程释放锁!");
//解锁,会唤醒其他等待/阻塞中的线程
reentrantLock.unlock();
}
}
}
1.5.锁超时案例
@Slf4j
public class TestReentrantLockDemo3 {
//锁对象
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.info("线程尝试获取锁...");
//立刻返回线程获取锁的结果,不会等待!!!
//if (!reentrantLock.tryLock()) {
// log.info("获取不到锁,执行结束!");
// return;
//}
try {
//如果线程获取到锁,可以立刻返回结果!
//如果线程没有获取到锁,它会等待一定的时间,在等待期间会不停尝试获取锁
//如果等待期间提前获取到锁,那么提前结束等待,返回获取到锁的结果,即返回true;
//如果等待时间到达仍然没有获取到锁,返回获取不到锁的结果,即返回false;
//该方法也支持当前线程某种状态被打断!!
if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)){
log.info("获取不到锁,执行结束!");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.info("没有获取到锁,线程被打断,执行结束!");
return;
}
try {
log.info("获取到锁,执行业务代码");
} finally {
log.info("线程t1执行完业务代码,释放锁!");
reentrantLock.unlock();
}
}, "t1");
//main线程获取锁
log.info("main主线程尝试获取锁");
reentrantLock.lock();
try {
log.info("main线程获取到锁,执行业务代码...");
} finally {
log.info("main线程执行完业务代码,释放锁!");
reentrantLock.unlock();
}
t1.start();
//打断t1线程的等待状态
//t1.interrupt();
}
}
1.6.公平锁
1>.ReentrantLock默认是不公平的,
可以在创建ReentrantLock对象的时候在构造函数中指定一个"true"让它变成公平锁;
(公平锁可以用来解决线程饥饿问题,但是一般没有必要,会降低并发度);
2>.Synchronized是非公平锁!
1.7.ReentrantLock条件变量
1.7.1.概述
1>.Synchronized中也有条件变量,就是之前的waitSet休息室
,当获取锁的线程由于条件不满足时进入waitSet等待;
2>.ReentrantLock的条件变量类似Synchronized条件变量,但是它比synchronized强大之处在于,它是支持多个条件变量
的,这就好比:
①.synchronized是那些不满足条件的线程都在一间休息室等消息;
②.而ReentrantLock支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按相应的休息室来唤醒;
3>.条件变量API使用要点
①.await()执行前需要获得锁;
②.await()执行后会释放锁,进入某个conditionObject等待;
③.await()的线程被唤醒(或打断、或超时)取重新竞争lock锁;
④.竞争lock锁成功后,从await()方法后(/位置)继续执行;
1.7.2.案例
@Slf4j
public class TestConditionDemo1 {
//锁对象
static ReentrantLock reentrantLock = new ReentrantLock();
//条件对象,一个锁对象可以对应多个条件对象,可以理解为休息室
static Condition waitCigaretteSet = reentrantLock.newCondition();
static Condition waitbreakfastSet = reentrantLock.newCondition();
//共享变量
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
reentrantLock.lock();
try {
log.info("有烟没?{}", hasCigrette);
while (!hasCigrette) {
log.info("没烟,先歇会...");
//获取锁的线程由于某些条件不满足,导致无法继续运行,而进入等待状态,可以理解为进入某个休息室内等待
//执行await(),会释放锁(唤醒其他处于阻塞状态的线程竞争锁)
//该方法可以让处于某种状态的线程被打断
waitCigaretteSet.await();
}
//正常干活
log.info("有烟没?{}", hasCigrette);
if (hasCigrette) {
log.info("有烟,可以开始干活...");
} else {
log.info("没烟,没有干成活!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}, "小南").start();
new Thread(() -> {
reentrantLock.lock();
try {
log.info("外卖到了没?{}", hasBreakfast);
while (!hasBreakfast) {
log.info("外卖没到,先歇会...");
waitbreakfastSet.await();
}
log.info("外卖到了没?{}", hasBreakfast);
if (hasBreakfast) {
log.info("外卖到了,可以干活...");
} else {
log.info("外卖没到,没干成活!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
reentrantLock.lock();
try {
log.info("外卖到了...");
hasBreakfast = true;
//唤醒对应休息室里面的线程
waitbreakfastSet.signal();
} finally {
reentrantLock.unlock();
}
}, "送外卖").start();
Thread.sleep(1000);
new Thread(() -> {
reentrantLock.lock();
try {
log.info("烟到了...");
hasCigrette = true;
//唤醒对应休息室里面的线程
waitCigaretteSet.signal();
} finally {
reentrantLock.unlock();
}
}, "送烟").start();
}
}