ReentrantLock 可重入锁
文章目录
1.特点
相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁(先进先出)构造参数为true表示公平锁。
- 支持多个条件变量(休息室)(条件不满足时进入waitSet等待)
与 synchronized 一样,都支持可重入。
2.基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
3.可重入
可重入指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。
static ReentrantLock lock = new ReentrantLock();
@Test
public void testReentrantLock() {
lock.lock();
try {
log.debug("进入main方法");
m1(); //锁重入
} finally {
lock.unlock();
}
}
public static void m1() {
lock.lock();
try {
log.debug("进入m1方法");
m2(); //锁重入
} finally {
lock.unlock();
}
}
public static void m2() {
lock.lock();
try {
log.debug("进入m2方法");
} finally {
lock.unlock();
}
}
4.可打断
reentrantLock的lock(); 是不可以打断的。lockInterruptibly();是可以打断的。
/**
* 可打断
*/
@Test
public void t2() {
Thread t1 = new Thread(() -> {
//如果没有竞争,则使用次方法获得lock对象锁
//如果有竞争,则进入阻塞队列,可以被其他线程的interrupt方法打断
try {
log.debug("尝试获得锁");
lock2.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("没有获得锁,返回");
return; //表示被打断,没有获得锁
}
try {
log.debug("获取到锁");
} finally {
lock2.unlock();
}
}, "t1");
//主线程加锁
lock2.lock();
t1.start();
//主线程谁3秒后打断
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
5.锁超时
没有参数的tryLock()
/**
* 锁超时
* trylock()
*/
static ReentrantLock lock3 = new ReentrantLock();
@Test
public void t3() {
Thread t1 = new Thread(() -> {
//尝试获得锁
if (!lock3.tryLock()) { //如果没有获得锁
log.debug("获取锁,立刻失败,返回");
return;
}
try {
log.debug("获得了锁");
} finally {
lock3.unlock();
}
}, "t1");
//先让主线程获得锁
lock3.lock();
log.debug("获得了锁");
//t1启动后不能获得锁
t1.start();
//主线程2秒后释放锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock3.unlock();
}
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
带参数的tryLock(long n,TimeUnit.单位)
/**
* tryLock(2, TimeUnit.SECONDS)
*/
@Test
public void t4(){
Thread t1 = new Thread(() -> {
//尝试获得锁
try {
if (!lock3.tryLock(1, TimeUnit.SECONDS)) { //如果没有获得锁
log.debug("获取锁,立刻失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("获得了锁");
} finally {
lock3.unlock();
}
}, "t1");
//先让主线程获得锁
lock3.lock();
log.debug("获得了锁");
//t1启动后不能获得锁
t1.start();
//主线程2秒后释放锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock3.unlock();
}
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
6.使用锁超时解决哲学家就餐问题
package com.concurrent.p4;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.TestQuestionPhilosopher2")
public class TestQuestionPhilosopher2 {
@Test
public void test1() {
Chopstick2 c1 = new Chopstick2("1");
Chopstick2 c2 = new Chopstick2("2");
Chopstick2 c3 = new Chopstick2("3");
Chopstick2 c4 = new Chopstick2("4");
Chopstick2 c5 = new Chopstick2("5");
new Philosopher2("苏格拉底", c1, c2).start();
new Philosopher2("柏拉图", c2, c3).start();
new Philosopher2("亚里士多德", c3, c4).start();
new Philosopher2("赫拉克利特", c4, c5).start();
new Philosopher2("阿基米德", c5, c1).start();
while (true) ;
}
}
@Slf4j(topic = "c.Philosopher2")
class Philosopher2 extends Thread {
private String name;
private Chopstick2 left;
private Chopstick2 right;
public Philosopher2(String name, Chopstick2 left, Chopstick2 right) {
super(name);
this.left = left;
this.right = right;
}
public void eat() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("eat...");
}
@Override
public void run() {
// while (true) {
// try {
// left.tryLock(1, TimeUnit.SECONDS);
// } catch (InterruptedException e) {
// e.printStackTrace();
// return;
// }
// try {
// right.tryLock(1, TimeUnit.SECONDS);
// } catch (InterruptedException e) {
// e.printStackTrace();
// return;
// }
// try {
// eat();
// } finally {
// left.unlock();
// right.unlock();
// }
// }
while (true) {
if (left.tryLock()) { //尝试获取左筷子
try {
if (right.tryLock()) { //尝试获取右筷子
try {
eat(); //都获取到开始eat
} finally {
right.unlock();
}
}
} finally {
left.unlock(); //***没有获取到right,则会释放left
}
}
}
}
}
class Chopstick2 extends ReentrantLock {
private String name;
public Chopstick2(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick2{" +
"name='" + name + '\'' +
'}';
}
}
7.公平锁
ReentrantLock默认是不公平的。
public ReentrantLock(boolean fair) { //如果fari=true,则为公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:按照进入阻塞队列的顺序,按照先进先出的顺序执行。
公平锁用来解决饥饿问题,但是没必要。一般使用tryLock()。
公平锁会降低并发程度。
8.条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒。
使用要点:
- await 前需要获得锁。
- await 执行后,会释放锁,进入 conditionObject 等待。
- await 的线程被唤醒(或打断、或超时)会重新竞争 lock 锁。
- 竞争 lock 锁成功后,从 await 后继续执行。
条件变量例子
static ReentrantLock lock = new ReentrantLock();
@Test
public void test1() {
//创建新的条件变量(休息室)
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Thread t1 = new Thread(() -> {
try {
lock.lock();
try {
log.debug("开始等待");
//进入休息室等待
condition1.await();
log.debug("结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
//t2线程5秒后唤醒休息室中的线程t1
try {
lock.lock();
try {
log.debug("休眠3秒...");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("唤醒休息室1中的线程");
condition1.signal();
} finally {
lock.unlock();
}
}, "t2");
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
9.用ReentrantLock改写送烟送外卖代码
package com.concurrent.p4;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 用ReentrantLock改写送烟,送外卖的例子
*/
@Slf4j(topic = "c.TestReentrantLockExample")
public class TestReentrantLockExample {
//static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
//定义ReentrantLock对象,true表示该锁为公平锁
static ReentrantLock lock = new ReentrantLock(true);
//定义两个条件变量
//吸烟室
static Condition smokingRoom = lock.newCondition();
//用餐室
static Condition launchRoom = lock.newCondition();
@Test
public void test1() throws InterruptedException {
//小南线程
Thread t1 = new Thread(() -> {
try {
lock.lock();
log.debug("有烟没?[{}]", hasCigarette);
//将if改为while多次判断
while (!hasCigarette) {
log.debug("没烟,先歇会!");
//没有烟就在吸烟室等待,此时可以释放锁
try {
smokingRoom.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没烟干不了");
}
} finally {
lock.unlock();
}
}, "小南");
t1.start();
//小女线程
Thread t2 = new Thread(() -> {
try {
lock.lock();
log.debug("有外卖没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
//没有外卖就在用餐室等待,此时可以释放锁
try {
launchRoom.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有外卖没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没外卖干不了");
}
} finally {
lock.unlock();
}
}, "小女");
t2.start();
//送外卖的
Thread t3 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();
hasTakeout = true;
log.debug("外卖到了");
launchRoom.signalAll();
} finally {
lock.unlock();
}
}, "送外卖的");
t3.start();
t1.join();
t2.join();
t3.join();
}
}