ReentrantLock
ReentrantLock
可以创建公平锁或非公平锁、响应中断、超时等待、按条件唤醒等。
在某些场景下,使用ReentrantLock
更适合,功能更强大。
源码解析
类结构
// 实现Lock接口
public class ReentrantLock implements Lock {
// 只有一个Sync同步变量
private final Sync sync;
// Sync继承自AQS,主要逻辑都在这里面
abstract static class Sync extends AbstractQueuedSynchronizer {
}
// Sync的两个子类,分别实现了公平锁和非公平锁
static final class FairSync extends Sync {
}
static final class NonfairSync extends Sync {
}
}
ReentrantLock
的类结构非常简单,并没有直接继承AQS抽象类,而是实现了Lock接口。
采用组合的方式使用Sync同步类实现锁的功能,而Sync同步类才是真正继承AQS抽象类。
Sync抽象类下面有两个子类,分别实现公平锁和非公平锁。
public interface Lock {
// 加锁
void lock();
// 加可中断的锁
void lockInterruptibly() throws InterruptedException;
// 尝试加锁
boolean tryLock();
// 一段时间内,尝试加锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 新建条件状态
Condition newCondition();
}
AQS定义了一些有关具体加锁、释放锁的抽象方法,留给子类去实现。
由于ReentrantLock使用的是
独占锁
,所以只需要实现独占锁相关的方法
ReentrantLock构造方法
创建ReentrantLock对象,可以指定使用公平锁还是非公平锁,默认使用非公平锁,非公平锁的性能更好。
// 默认的构造方法,使用非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 传true,可以指定使用公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
NonfairSync源码
从父类ReentrantLock
的加锁方法入口看起:
public class ReentrantLock implements Lock {
// 加锁入口方法
public void lock() {
// 调用Sync中加锁方法
sync.lock();
}
}
子类非公平锁NonfairSync
的加锁方法:
// 非公平锁
static final class NonfairSync extends Sync {
// 加锁
final void lock() {
// 1. 先尝试加锁(使用CAS设置state=1)
if (compareAndSetState(0, 1)) {
// 2. 如果加锁成功,就把当前线程设置为持有锁线程
setExclusiveOwnerThread(Thread.currentThread());
} else {
// 3. 如果加锁失败,再调用父类AQS中实际的加锁逻辑
acquire(1);
}
}
}
先尝试使用CAS加锁(也就是把state从0设置成1),加锁成功,就把当前线程设置为持有锁线程。
在锁竞争不激烈的情况下,很大概率可以加锁成功,也就不用走else中复杂的加锁逻辑了。
如果没有加锁成功,还是需要走else中调用父类AQS的acquire()
方法,而acquire()
方法又需要调用子类的tryAcquire()
方法。
调用链路如下:
实际的加锁逻辑在Sync.nonfairTryAcquire()
方法里面。
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁的最终加锁方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 1. 获取同步状态
int c = getState();
// 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 3. 加锁成功,就把当前线程设置为持有锁线程
setExclusiveOwnerThread(current);
return true;
}
// 4. 如果当前线程已经持有锁,执行可重入的逻辑
} else if (current == getExclusiveOwnerThread()) {
// 5. 加锁次数+acquires
int nextc = c + acquires;
// 6. 超过tnt类型最大值,溢出了
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 7. 加锁失败,返回false
return false;
}
}
总结-非公平锁的加锁逻辑也很简单,大致分为三步:
- 判断同步状态state,如果state=0表示无锁,就尝试加锁,如果加锁成功,就更新state和线程owner。
- 如果线程owner就是当前线程,执行可重入锁的逻辑,更新state值。
- 否则加锁失败,返回false。
再看释放锁的调用流程,公平锁和非公平锁流程是一样的,最终都是执行Sync.tryRelease()
方法:
本质是 更新同步状态 state 和 线程 owner 。
abstract static class Sync extends AbstractQueuedSynchronizer {
// 释放锁
protected final boolean tryRelease(int releases) {
// 1. 同步状态减去释放锁次数
int c = getState() - releases;
// 2. 校验当前线程不持有锁,就报错
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
boolean free = false;
// 3. 判断同步状态是否等于0,无锁后,就删除持有锁的线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
FairSync源码
最终的加锁方法是FairSync.tryAcquire()
,具体源码:
static final class FairSync extends Sync {
// 实现父类的加锁逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 1. 获取同步状态
int c = getState();
// 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
if (c == 0) {
// 3. 判断当前线程是不是头节点的下一个节点(讲究先来后到)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
// 4. 如果当前线程已经持有锁,执行可重入的逻辑
} else if (current == getExclusiveOwnerThread()) {
// 5. 加锁次数+acquires
int nextc = c + acquires;
// 6. 超过tnt类型最大值,溢出了
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 判断当前线程是不是头节点的下一个节点(讲究先来后到)(AQS)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
}
公平锁的加锁逻辑跟非公平锁大致相同,唯一的区别是在第三步,更新同步状态state之前,增加了一个判断条件hasQueuedPredecessors()
方法,用于判断当前线程是不是头节点的下一个节点。
因为等待的线程都要在同步队列中排队,同步队列的头节点是空节点,头节点的下一个节点相当于第一个有效节点,这个节点优先获取锁,在这里体现了公平锁的逻辑。
公平锁的释放锁逻辑跟非公平锁一样,上面已经讲过。
示例
public class ReentrantLockDemo {
public static void main(String[] args) {
// 1. 创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock();
// 2. 加锁
lock.lock();
try {
// 3. 这里执行具体的业务逻辑
} finally {
// 4. 释放锁
lock.unlock();
}
}
}
ReentrantLock
的使用非常简单,调用lock()
方法加锁,unlock()
方法释放锁;需要配合try/finally关键字使用,保证在代码执行出错的时候也能释放锁。
手写锁
要求:同一时间只能有一个线程持有锁,不要求可重入(无视反复加锁)
public class Main {
public static void main(String[] args) throws InterruptedException {
}
/**
* 自行实现一个最普通的独占锁
* 要求:同一时间只能有一个线程持有锁,不要求可重入
*/
private static class MyLock implements Lock {
/**
* 设计思路:
* 1. 锁被占用,那么exclusiveOwnerThread应该被记录,并且state = 1
* 2. 锁没有被占用,那么exclusiveOwnerThread为null,并且state = 0
*/
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 无需可重入功能,如果是当前线程直接返回true
if(isHeldExclusively()) return true;
// CAS操作进行状态替换
if(compareAndSetState(0, arg)){
// 成功后设置当前的所有者线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0)
// 没加锁情况下是不能直接解锁的
throw new IllegalMonitorStateException();
// 只有持有锁的线程才能解锁
if(isHeldExclusively()){
// 设置所有者线程为null
setExclusiveOwnerThread(null);
// 状态变为0
setState(0);
return true;
}
return false;
}
@Override
protected boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
protected Condition newCondition(){
// 直接用现成的
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
}