ReentrantLock 是java.util.concurrent.locks包下的类。
相对于synchronized,它具备如下特性:
可中断。
可以设置超时时间。
可以设置公平锁。
支持多个条件变量。即可以有个多个waitset等待队列。
与synchronized都支持可重入。
ReentrantLock的基本语法:
// 获取锁
reentrantLock.lock();
try {
// 临界区代码
} finally {
// 释放锁
reentrantLock.unlock();
}
1、可重入
可冲入是指 同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可冲入锁,那么第二次获取锁时,自己会被锁挡住。
测试代码示例:
/**
* ReentrantLock 可重入 代码测试。
*/
@Slf4j(topic = "test.ReentrantLockTest1")
public class ReentrantLockTest1 {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
reentrantLock.lock();
try {
log.debug("enter main...");
method1();
} finally {
reentrantLock.unlock();
}
}
public static void method1() {
reentrantLock.lock();
try {
log.debug("enter method1...");
method2();
} finally {
reentrantLock.unlock();
}
}
public static void method2() {
reentrantLock.lock();
try {
log.debug("enter method2...");
} finally {
reentrantLock.unlock();
}
}
}
执行结果:
2、可中断
reentrantLock.lockInterruptibly();
如果没有竞争,此方法会让当前线程t1 获取到reentrantLock对象锁。
如果有竞争,会进入阻塞队列,但可以被其它线程用 t1.interrupt()方法打断阻塞,进而让线程t1继续执行。
可以防止线程无限制的等待下去(因为 t1.interrupt() 可以打断t1线程).
测试代码示例如下:
/**
* ReentrantLock 可中断特性 代码测试。
*/
@Slf4j(topic = "test.ReentrantLockTest2")
public class ReentrantLockTest2 {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("尝试获取reentrantLock锁");
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("没有获取reentrantLock锁,返回");
return;
}
try {
log.debug("获取到reentrantLock锁");
} finally {
reentrantLock.unlock();
}
},"t1");
// main线程获得reentrantLock锁
reentrantLock.lock();
// t1线程启动后,
// 如果reentrantLock存在竞争,t1线程无法获取锁;
// 如果没有竞争,reentrantLock.lockInterruptibly()会让t1线程获取到reentrantLock对象锁。
t1.start();
Thread.sleep(1000);
log.debug("打断t1线程,让t1线程进入catch(InterruptedException)");
t1.interrupt();
}
}
执行结果:
3、设置超时时间
boolean reentrantLock.tryLock(); 可以用来尝试获得锁。
boolean reentrantLock.tryLock(long timeout,TimeUnit unit); 可以用来尝试获得锁,并设置超时时间。调用此方法期间,可以调用 t1.interrupt()方法进行打断。
测试代码示例:
/**
* ReentrantLock 设置超时时间 代码测试。
*/
@Slf4j(topic = "test.ReentrantLockTest3")
public class ReentrantLockTest3 {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("尝试获取锁");
if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.error("获取不到锁,被打断。");
return;
}
try {
log.debug("获取到锁");
} finally {
reentrantLock.unlock();
}
},"t1");
// main线程获得reentrantLock锁
try {
reentrantLock.lock();
log.debug("main线程获取到锁");
t1.start();
Thread.sleep(1000);
} finally {
log.debug("main线程释放锁");
reentrantLock.unlock();
}
t1.interrupt();
}
}
使用tryLock()解决哲学家就餐死锁问题,代码示例如下:
/**
* reentrantLock.lock(); 解决哲学家就餐问题。
*/
@Slf4j(topic = "test.ReentrantLockTest4")
public class ReentrantLockTest4 {
public static void main(String[] args) {
// 五根筷子
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
// 五个哲学家吃饭
new Philosopher("哲学家1",c1,c2).start();
new Philosopher("哲学家2",c2,c3).start();
new Philosopher("哲学家3",c3,c4).start();
new Philosopher("哲学家4",c4,c5).start();
new Philosopher("哲学家5",c5,c1).start();
}
// 哲学家类
@Slf4j(topic = "test.Philosopher")
static class Philosopher extends Thread {
private Chopstick left;
private Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// synchronized 会造成死锁
// // 尝试获取左手筷子
// synchronized (left) {
// // 尝试获取右手筷子
// synchronized (right) {
// eat();
// }
// }
// 尝试获取左手筷子
if (left.tryLock()) {
try {
// 尝试获取右手筷子
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
private void eat() {
log.debug("eating...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 筷子类
@Slf4j(topic = "test.Chopstick")
static class Chopstick extends ReentrantLock {
private String name;
public Chopstick (String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + "}";
}
}
}
4、设置公平锁
synchronized是不公平锁,锁释放时,阻塞队列中的多个线程去竞争。
ReentrantLock默认是不公平的,但可以设置为公平锁(即锁释放时,根据进入阻塞队列的顺序,先来先得)。一般不会设置公平锁,会降低并发度。
源码如下:可以通过构造方法设置公平性。
5、条件变量
synchronized中也有条件变量,就是monitor中的waitset休息室,当条件不满足时进入waitset等待。
ReentrantLock的条件变量比synchronized的强大之处在于,它是支持多个条件变量的,这就好比
synchronized是哪些不满足条件的线程都在一间休息室等消息。
而ReentrantLock支持多间休息室,有专门等烟的休息室、专门等早餐的休息室,唤醒时也是按休息室来唤醒。
使用流程:
await前需要获得锁。
await执行后,会释放锁,进入conditionObject等待。
await的线程被唤醒(或打断、或超时),重新竞争锁。
竞争锁成功后,从await后继续执行。
代码示例如下:
/**
* ReentrantLock 之 await、newCondition 的使用:条件等待
*/
@Slf4j(topic = "test.ReentrantLockTest5")
public class ReentrantLockTest5 {
private static boolean hasCigarette = false; // 是否有烟的标识
private static boolean hasTakeout = false; // 是否外卖的标识
private static ReentrantLock lock = new ReentrantLock(); // 创建ReentrantLock锁
private static Condition waitCogaretteSet = lock.newCondition();// 等烟的休息室
private static Condition waitTakeoutSet = lock.newCondition(); // 等外卖的休息室
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
lock.lock(); // 获取锁
try {
log.debug("有烟吗?[{}]",hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
// await前需要获得锁;
// await执行后,会释放锁,进入waitCogaretteSet等待。
waitCogaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
lock.unlock(); // 释放锁
}
},"小南").start();
new Thread(() -> {
lock.lock(); // 获取锁
try {
log.debug("外卖送到没?[{}]",hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
// await前需要获得锁;
// await执行后,会释放锁,进入waitTakeoutSet等待。
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
lock.unlock(); // 释放锁
}
},"小刘").start();
Thread.sleep(1000);
new Thread(() -> {
try {
lock.lock();
hasCigarette = true; // 有烟了
waitCogaretteSet.signal();// 等烟休息室中唤醒一个等烟线程
} finally {
lock.unlock();
}
},"送烟的").start();
Thread.sleep(1000);
new Thread(() -> {
try {
lock.lock();
hasTakeout = true; // 有外卖了
waitTakeoutSet.signal();// 等外卖休息室中唤醒一个等外卖线程
} finally {
lock.unlock();
}
},"送外卖的").start();
}
}