一、ReentranLock
ReentranLock属于JUC并发工具包下的类,相当于 synchronized具备如下特点
● 可中断
● 可以设置超时时间
● 可以设置为公平锁(防止线程出现饥饿的情况)
● 支持多个条件变量
与 synchronized一样,都支持可重入
基本语法(synchronized在关键字级别保护临界区, reentrantLock是在对象的级别来保护临界区)
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁(无论是否出现异常,均会将锁释放)
reentrantLock.unlock();
}
lock()与unlock()是成对出现的
1.1 可重入
可重入是指同一个线程对象如果首次获得这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自身也会被锁挡住
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test")
public class Test {
// 创建锁重入对象
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 加锁
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();
}
}
}
运行结果:(锁重入成功)
1.2 可打断——lockInterruptibly
在等待锁的过程中其他线程可以用interruput()方法终止等待
import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test")
public class Test {
// 创建锁重入对象
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1=new Thread(()->{
try {
// 尝试获取锁,但可以被打断(如果没有别的线程竞争锁,此方法就会获取lock对象上的锁)
/*若有竞争进入阻塞队列等待*/
log.debug("尝试获得锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("没有获取锁,返回");
return;
}try {
log.debug("获取到锁");
}finally {
// 将锁释放掉
lock.unlock();
}
},"t1");
// 主线程先对其进行加锁后,t1线程才启动
lock.lock();
t1.start();
// 主线程睡眠1s后打断t1
Sleeper.sleep(1);
t1.interrupt();
}
}
运行结果:(成功打断t1线程)
1.3 锁超时
锁超时
:在获取锁的过程中,如果其他线程持有锁一直未释放,去尝试获取锁的线程也不会死等,而是等待一段时间,若这段时间超过对方仍未释放锁,则放弃等待,获取锁失败
可打断属于一种被动的避免无限等待(死等)方式;而锁超时以主动的方式避免死等
1、无其他线程竞争锁:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test")
public class Test {
// 创建锁重入对象
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
// 尝试获取锁,返回值为布尔型 【成功:获取锁 失败:不可获得锁,不会进入阻塞队列等待】
if (!lock.tryLock()) { //失败则立刻返回(没有任何等待时间)
log.debug("获取锁失败"); // false
return;
}
try {
// 执行临界区代码
log.debug("成功获取锁");
} finally {
lock.unlock(); // 释放锁
}
});
}
}
运行结果:
2、存在其他线程竞争(立刻结束):
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test")
public class Test {
// 创建锁重入对象
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
// 尝试获取锁,返回值为布尔型 【成功:获取锁 失败:不可获得锁,不会进入阻塞队列等待】
if (!lock.tryLock()) {
log.debug("获取锁失败"); // false
return;
}
try {
// 执行临界区代码
log.debug("成功获取锁");
} finally {
lock.unlock(); // 释放锁
}
});
// 主线程先对lock对象加锁
lock.lock();
log.debug("成功获取锁");
t1.start();
}
}
运行结果:
3、存在其他线程竞争(等待一段时间):尝试等待1s,1s内若主线程还未释放锁再结束
哲学家就餐问题便可以使用tryLock()解决
1.4 公平锁
ReentrantLock 默认是不公平的。当一个线程持有锁,其他线程就会进入阻塞队列等待,当锁的持有者释放锁时,阻塞队列中等待的线程会一拥而上,谁先争抢到锁谁便是Owner
,而不会按进入阻塞队列的先后顺序先来先得
(通过查看源码发现其构造方法中有一个带boolean类型参数的方法,其参数fair默认为false,可以修改其布尔值保证其公平性)公平锁一般没有必要,会降低并发度
二、ReentranLock条件变量
2.1 简介
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
● synchronized 是那些不满足条件的线程都在一间休息室等消息
● 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤
醒
使用要点:
● await 前需要获得锁
● await 执行后,会释放锁,进入 conditionObject 等待
● await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
● 竞争 lock 锁成功后,从 await 后继续执行
使用例子:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test24")
public class Test24 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
// 等待烟的休息室(创建一个新的条件变量)
static Condition waitCigaretteSet = ROOM.newCondition();
// 等外卖的休息室(创建一个新的条件变量)
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) {
new Thread(() -> {
// 尝试获取ReentrantLock
ROOM.lock();
try {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
// 进入等烟休息室等待
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
// 解锁
ROOM.unlock();
}
}, "小南").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小女").start();
// 送外卖线程
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasTakeout = true;
// 唤醒线程
waitTakeoutSet.signal();
} finally {
ROOM.unlock();
}
}, "送外卖的").start();
// 送烟线程
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasCigarette = true;
// 唤醒线程
waitCigaretteSet.signal();
} finally {
ROOM.unlock();
}
}, "送烟的").start();
}
}
运行结果: