1、可重入
可重入是指同一线程如果首次获得了这把锁,那么因为是这把锁的拥有者,因此有权利再次获取这边锁。
/**
* 测试ReentrantLock可重入性
*/
@Slf4j(topic = "c.TestReentrant")
public class TestReentrant {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
log.debug("执行方法1");
try {
lock.lock();
log.debug("获得锁,调用方法2");
method2();
}finally {
lock.unlock();
}
}
public static void method2() {
log.debug("执行方法2");
try {
lock.lock();
log.debug("获得锁");
}finally {
lock.unlock();
}
}
}
2、可打断
可打断指在线程等待过程中,可以被打断。
- 方法:lock.lockInterruptibly() 尝试获取锁,如果获取不到锁,进入等待;等待过程中可以被打断。
/**
* 测试ReentrantLock可打断性
*/
@Slf4j(topic = "c.TestInterruptible")
public class TestInterruptible {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
// 尝试获取锁
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取锁失败,退出.");
return;
}
try {
log.debug("获取到锁,执行后续代码...");
} finally {
lock.unlock();
}
}, "t1");
// 主线程先获取了锁
lock.lock();
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("打断t1线程...");
t1.interrupt();
}
}
当使用如果获取锁调用的是lock.lock(),那么线程等待不会被打断,
/**
* 测试ReentrantLock可打断性
*/
@Slf4j(topic = "c.TestInterruptible")
public class TestInterruptible {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
// try {
// // 尝试获取锁
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// e.printStackTrace();
// log.debug("获取锁失败,退出.");
// return;
// }
try {
// 尝试获取锁
lock.lock();
log.debug("获取到锁,执行后续代码...");
} finally {
lock.unlock();
}
}, "t1");
// 主线程先获取了锁
lock.lock();
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("打断t1线程...");
t1.interrupt();
}
}
3、锁超时
可打断能避免一直等待,防止死锁。锁超时呢,是主动性的。线程尝试获取锁,如果获取不到锁,不会一直等待。
-
方法:lock.tryLock()
-
方法:lock.tryLock(long timeout, TimeUnit unit)
- timeout :超时时间
- unit:时间单位
-
测试无参tryLock()
/** * 测试ReentrantLock 锁超时-tryLock无参 */ @Slf4j(topic = "c.TestTryLock01") public class TestTryLock01 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { if(!lock.tryLock()) { log.debug("没有获取锁,退出"); return; } try { log.debug("获取到锁,执行后续代码。。。"); }finally { lock.unlock(); } }, "t1"); // 主线程先获取了锁 lock.lock(); t1.start(); } }
-
测试带参tryLock
/** * 测试ReentrantLock 锁超时-tryLock带参 */ @Slf4j(topic = "c.TestTryLock02") public class TestTryLock02 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { if(!lock.tryLock(1, TimeUnit.SECONDS)) { log.debug("没有获取锁,退出"); return; } } catch (InterruptedException e) { e.printStackTrace(); log.debug("获取锁失败,退出"); return; } try { log.debug("获取到锁,执行后续代码。。。"); }finally { lock.unlock(); } }, "t1"); // 主线程先获取了锁 lock.lock(); Thread.sleep(100); t1.start(); } }
4、锁超时解决哲学家进餐问题
- 分析:
之前之所以会出现死锁现象,因为线程在获取另外一根筷子(锁)时,如果获取失败,就会无限等待;这里我们用tryLock超时等待方式,线程在获取另外一根筷子(锁)时,如果获取失败,不等待或者有时限的等待,同时放弃已经获取的筷子;
/**
* 筷子类
*/
@Data
@AllArgsConstructor
public class Chopstick extends ReentrantLock {
/** 筷子名称 */
private String name;
}
/**
* 哲学家类
*/
@Slf4j(topic = "c.Philosopher")
@Data
public 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) {
// 尝试获取左手边的筷子
if (left.tryLock()) {
try {
// 尝试获取右手边的筷子
if (right.tryLock()) {
try {
// 拿到了一双筷子,开始吃饭
eat();
} finally {
// 然后释放锁
right.unlock();
}
}
}finally {
// 1、正常执行,吃饭后放下筷子
// 2、获取右手边筷子失败,那么就放下已经获取到的左筷子,防止死锁
left.unlock();
}
}
}
}
/**
* 吃饭
*/
private void eat() {
log.debug("eating ...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 测试类
*/
@Slf4j(topic = "c.TestPhilosopherDinner")
public class TestPhilosopherDinner {
public static void main(String[] args) {
// 创建5根筷子
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");
// 创建5个哲学家线程
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
5、公平锁与不公平锁
公平:在等待队列中等待的线程,重新竞争锁对象的时候,如果遵循先来来获取,就是公平的;否则就是不公平的。
-
在Monitor EntrList中线程竞争锁对象非公平的;
-
ReentrantLock 锁默认也是不公平的;在构造函数中设置布尔参数值为true,为公平锁
// 源码ReentrantLock 构造方法 // 无参 public ReentrantLock() { sync = new NonfairSync(); } // 有参 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
关于底层具体怎么实现公平与不公平,我们后续详解。
6、条件变量
ReentrantLock的条件变量比synchronized强大支出在于,它支持多个条件变量(对象)。
- synchronized那些不满足条件的线程进入一个等待队列
- 而ReentrantLock支持多个条件变量对象(队列);线程可根据情况选择进入那个队列等待;同样根据条件唤醒在不同等待队列中等待的线程。
过程:
- 等待状态的线程获取锁对象。
- 因为不满足某个条件,进入ConditionObject等待
- ConditionObject等待的线程被唤醒(或打断或超时),重新竞争锁对象
相关对象和方法
- 对象:Condition 条件变量,由ReentrantLock对象创建
- 方法:condition.await()/condition.await(long tiomout) 进入条件变量等待
- 方法:condition.signal()/condition.signalAll() 唤醒在条件变量上等等的线程
示例:以之前工地施工为例,李师傅等电接通;王师傅等柴油继续开卡车为例
@Slf4j(topic = "w.TestCondition")
public class TestCondition {
public static void main(String[] args) throws InterruptedException {
ReentrantLock room = new ReentrantLock();
// 是否又电
AtomicBoolean hasElectric = new AtomicBoolean(false);
// 是否有柴油
boolean hasDiesel = false;
// 等电的休息室
Condition electric = room.newCondition();
// 等柴油的休息室
Condition diesel = room.newCondition();
new Thread(() -> {
room.lock();
try {
log.debug("电有了没?[{}]", hasElectric);
while (!hasElectric.get()) {
log.debug("没电,先歇会!");
try {
electric.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("电接通了?[{}]", hasElectric);
if (hasElectric.get()) {
log.debug("开始干活!");
}
} finally {
room.unlock();
}
}, "李师傅").start();
new Thread(() -> {
room.lock();
try {
log.debug("柴油送到没?[{}]", hasElectric);
while (!hasDiesel) {
log.debug("柴油没送到,先歇会!");
try {
diesel.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("柴油送到没?[{}]", hasElectric);
if (hasDiesel) {
log.debug("开始干活!");
}
} finally {
room.unlock();
}
}, "王师傅").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
room.lock();
hasElectric.set(true);
log.debug("电接通了!");
electric.signal();
} finally {
room.unlock();
}
}, "工人").start();
}
}
仓库地址:https://gitee.com/gaogzhen/concurrent