目录
ReentrantLock
ReentrantLock语法
ReentrantLock可重入
ReentrantLock可打断
ReentrantLock锁超时
ReentrantLock解决哲学家就餐问题
ReentrantLock公平锁
ReentrantLock条件变量
ReentrantLock
ReentrantLock 相比于synchronized的特点 :
- 可中断:比如A线程拥有锁,B线程能给他取消掉
- 可以设置超时时间,规定一段时间竞争锁,如果获取不到锁,那么就不竞争了做一些其他的逻辑.
- 可以设置为公平锁 ,防止线程饥饿的情况,大家都在排队等,先到的先得到锁.
- 支持多个条件变量 ,相当于waitSet(条件不满足等待的地方),支持多个WaitSet里面等
相当于一个房子有不同的休息室,对其进行细分.
相同点 : 都是支持可重入的.同一个线程可以对同一个对象反复加锁.
ReentrantLock语法
//创建一个ReentrantLock对象
private static ReentrantLock lock =new ReentrantLock();
public static void main(String[] args) {
//调用lock方法加锁
lock.lock();
try{
//临界区的代码
}finally {
//必须对其进行解锁,所以放在finally块
lock.unlock();
}
}
ReentrantLock可重入
Synchronized和ReentrantLock都是可重入锁.
可重入锁也就是同一个线程首次获得了这把锁,也就是称为这把锁的拥有者,因此有权利再次获取这把锁,也就是同一个线程可以对同一个对象反复加锁
相反之,就有不可重入锁.
不可重入锁就是当第二次获取锁的时候,就会被锁挡住.
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.ReentrantFeatures")
public class ReentrantFeatures {
/**
* ReentrantLock可重入特性演示
* @param args
*/
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
log.debug("enter main");
m1();
}finally {
lock.unlock();
}
}
public static void m1() {
lock.lock();
try {
log.debug("enter m1");
m2();
}finally {
lock.unlock();
}
}
public static void m2() {
lock.lock();
try {
log.debug("enter m2");
}finally {
lock.unlock();
}
}
}

如果是不可重入锁,第二次获取锁的时候就会被挡住
ReentrantLock可打断
ReentrantLock可打断支持可打断的特性,通过调用ReentrantLock对象的lockinterruptibly()方法,其他线程就可以使用interrupt()方法对其打断可以防止无限制的等待下去,避免死锁.
在有竞争的情况下,如果该lockinterruptibly()方法没有被其他线程打断,那么和lock方法一样会死等线程释放锁.知道获取到锁
@Slf4j(topic = "c.LockInterruptiblyFeatures")
public class LockInterruptiblyFeatures {
/**
* 演示ReentrantLock可打断的特性
*/
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
log.debug("尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("没有获取到锁,被打断,返回");
return;
}
try {
log.debug("获取到锁");
}finally {
lock.unlock();
}
});
log.debug("获取到锁");
lock.lock();
t1.start();
try{
log.debug("1s后打断t1");
//主线程1s后打断t1线程
Thread.sleep(1000);
t1.interrupt();
}finally {
lock.unlock();
}
}
}

通过调用ReentrantLock对象的lockinterruptibly()方法,其他线程就可以使用interrupt()方法对其打断可以防止无限制的等待下去,避免死锁.
ReentrantLock锁超时
可打断的这个特点是被动地避免死等,由其他线程调用interrupt方法让其不要继续死等.
锁超时是主动地避免死等,在获取锁的过程中,如果其他线程持有者锁一直没有释放,尝试获取锁的线程也不会死等,会设置一个超时时间,如果超过了这个超时时间,如果其他线程仍然没有释放锁,那么当前线程获取锁失败,避免无限制的等待下去,也就避免了死锁.
通过tryLock()方法可以实现锁超时

tryLock()方法不带参数 :-->没有设置超时时间
- 获取锁成功返回true,获取锁失败返回false
tryLock()方法带参数 :-->表示可以设置超时时间
- 获取锁成功返回true,如果超过超时的时间还没有获取到锁,就获取锁失败,返回false
@Slf4j(topic = "c.TryLock")
public class TryLock {
/**
* 演示ReentrantLock锁超时的情况
*/
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
if(!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("没有获取到锁,立即返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
//tryLock也可以被打断,没有获取到锁
log.debug("被打断,没有获取到锁,立即返回");
return;
}
log.debug("获取到锁");
try {
//执行临界区代码
log.debug("执行临界区代码");
}finally {
//解锁
lock.unlock();
}
},"t1");
log.debug("获取锁");
lock.lock();
t1.start();
try{
Thread.sleep(2000);
}finally {
log.debug("释放锁");
lock.unlock();
}
}
}
没有超过超时时间获取到锁

超过超时时间没有获取到锁

ReentrantLock解决哲学家就餐问题
package com.example.demo.Controller.DiningPhilosophersProblem;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Philosophers")
public class Philosophers extends Thread{
private Chopstick left;//左手筷子
private Chopstick right;//右手筷子
public Philosophers(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while(true) {
if(left.tryLock()) {//尝试获取左手筷子
try{
if(right.tryLock()) {//尝试获取到右手筷子
try{
//既获得了左手筷子,又获得了右手筷子.就可以吃饭了
eat();
}finally {
right.unlock();
}
}
//如果哲学家获取到了左手筷子,但是没有获取到右手筷子
//那么哲学家同时也会释放左手筷子
}finally {
left.unlock();
}
}
}
}
private void eat() {
log.debug("eating...");
try {
Thread.sleep(200);//思考1s钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

分析一下,为啥这样能解决死锁,对于synchronized,也就是没有获取到,就一直死等
而对于ReentrantLock来说,如果没有获取到锁就不等了,防止无限制等待.如果获取到了左手筷子,但没有获取到右手筷子,那么他也会同时释放左手筷子,让其他哲学家先吃
ReentrantLock公平锁
所谓公平锁,也就是先到先得. 如果多个线程获取锁的时候,就会去等待队列EntryList中去排队等待,如果是公平锁也就是排在前面的线程先获取到锁,排在后面的后获取到锁.
而非公平锁,并不是先到先得,而是谁竞争到了谁就先获取到锁.
ReentrantLock条件变量
对于条件变量我们可以理解为 synchronized的WaitSet,也就是条件不满足的时候就调用wait方法去WaitSet中等待,这个WaitSet就相当于条件变量.
对于ReentrantLock来说 ReentrantLock支持多个条件变量,将其条件更加细分
就相当于 :
- synchronized是那些不满足线程都在一间休息室(同一个WaitSet)中等待.
- 对于ReentrantLock来说支持多间休息室,有专门等外卖的休息室,有专门等待烟的休息室,唤醒也是按照休息室来唤醒的.
使用要点:
- await 前需要获得锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.TestDemo1")
public class TestDemo1 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
//要想进入条件变量(消息室) 必须获取到锁
lock.lock();
//一个锁对象可以由多个条件变量(多个休息室) 将其细分
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
try{
//去休息室1 里面等待
condition1.await();
}finally {
lock.unlock();
}
//唤醒条件变量1(休息室1)的线程
condition1.signal();
//唤醒条件变量2(休息室2)所有的线程
condition1.signalAll();
}
}
Condition代码练习
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.TestWaitNotify3")
public class TestCondition {
private static boolean isCigarette = false;
private static boolean isTakeOut = false;
//每一个人不用去同一间休息室等了,而是去不同的休息室等待,这样就避免了通一间休息室全部唤醒的问题->虚假唤醒
private static ReentrantLock ROOM = new ReentrantLock();//一个大房间有多间休息室
private static Condition cigaretteWaitSet = ROOM.newCondition();
private static Condition takeOutWaitSet = ROOM.newCondition();
public static void main(String[] args) throws InterruptedException {
//小南等烟
new Thread(()->{
ROOM.lock();
try{
log.debug("是否有香烟 : " +isCigarette);
if(!isCigarette){
log.debug("没有香烟,干不了活,等一会");
try {
cigaretteWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("小南开始干活");
}finally {
ROOM.unlock();
}
},"小南").start();
//小女等外卖
new Thread(()->{
ROOM.lock();
try{
log.debug("是否有外卖 : " +isTakeOut);
if(!isTakeOut){
log.debug("没有外卖,干不了活,等一会");
try {
takeOutWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("小女干活");
}finally {
ROOM.unlock();
}
},"小女").start();
Thread.sleep(1000);
new Thread(()->{
ROOM.lock();
try {
isTakeOut = true;
log.debug("外卖到了");
takeOutWaitSet.signal();
}finally {
ROOM.unlock();
}
},"送外卖的").start();
Thread.sleep(1000);
new Thread(()->{
ROOM.lock();
try {
isCigarette = true;
log.debug("烟到了");
cigaretteWaitSet.signal();
}finally {
ROOM.unlock();
}
},"送烟的").start();
}
}

对之前的代码做出改进 使用ReentrantLock,这样两个人就不用去同一间休息室等待了,可以去同一个房间的不同休息室等待,这样当唤醒时候,不会把所有人都唤醒.



















