特点
相比于synchronized,ReentrantLock具有可中断,可以设置超时时间,可以设置为公平锁,支持多个条件变量的特点,它和synchronized一样,都支持可重入
基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获取锁时,自己也会被挡住
可重入验证代码
@Slf4j(topic = "ch.ReentrantLockTest01")
public class ReentrantLockTest01 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("method1");
//验证可重入
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("method3");
} finally {
lock.unlock();
}
}
}
可打断
可中断锁是指抢占过程可以被中断的锁,如下:
@Slf4j(topic = "ch.ReentrantLockTest02")
public class ReentrantLockTest02 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动");
try {
//可打断的上锁
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("被打断");
return;
}
log.debug("上锁成功");
lock.unlock();
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
Thread.sleep(1);
log.debug("执行打断");
t1.interrupt();
lock.unlock();
}
}
锁超时
其实这里用了tryLock方法实现了,tryLock有两种使用方法,一种是直接tryLock,返回值是Boolean对象,表示的是用来尝试获取锁,如果成功就返回true,如果失败就返回false,这个方法无论成功失败都会立刻返回。tryLock还有一个重载方法public boolean tryLock(long timeout, TimeUnit unit),表示在这个时间范围内成功获取了锁返回true,没获得就返回false。实例如下:
//测试锁超时
@Slf4j(topic = "ch.ReentrantLockTest03")
public class ReentrantLockTest03 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动");
if (!lock.tryLock()) {
log.debug("获取锁失败,返回");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
log.debug("主线程获取了锁");
lock.lock();
t1.start();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
使用tryLock解决哲学家就餐问题
//使用tryLock解决哲学家就餐问题
@Slf4j(topic = "ch.ReentrantLockTest04")
public class ReentrantLockTest04 {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1", true);
Chopstick c2 = new Chopstick("2", true);
Chopstick c3 = new Chopstick("3", true);
Chopstick c4 = new Chopstick("4", true);
Chopstick c5 = new Chopstick("5", true);
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 = "ch.Chopstick")
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name, boolean fair) {
super(fair);
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
@Slf4j(topic = "ch.Philosopher")
class Philosopher extends Thread {
Chopstick left;
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 {
left.unlock();
}
}
}
}
private void eat() {
log.debug("eating....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
公平锁
ReentrantLock 默认是不公平的,公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。
ReentrantLock的两个构造方法:
// 默认非公平
public ReentrantLock() {
sync = new NonfairSync();
}
// 根据传参来实现公平或非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
条件变量
synchronized中也有条件变量,起的作用类似于synchronized中的那个waitSet休息室,当条件不满足时,进入waitSet等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
*synchronized的那些不满足条件的线程都在一间休息室等消息
*而 ReentrantLock 支持多间休息室,可以按照不同的资源等待条件new不同的“休息室”,唤醒时也是按照不同的条件来唤醒的
使用要点:
await前需要获得锁
await执行后,会释放锁,进入conditionObject等待
await线程被唤醒(或打断,或超时)后需重新竞争lock锁
竞争lock锁成功后,从await后继续执行