上一篇文章已完整展示了等待队列的管理(添加结点、移除取消结点)、独占模式下的acquire操作、acquire中断取消、前驱如何通知后继。这些知识已足够用来实现一个可重入锁。
本篇通过java.util.concurrent.locks.ReentrantLock
类的源码来分析如何实现可重入锁。
可重入锁
可重入锁就是说当线程拥有这把锁的时候,它再次请求锁是成功的;当线程释放锁时,如果持有锁的线程对锁的请求次数大于释放次数,则该线程仍然拥有锁,直到请求次数与释放次数相等时才真正释放锁。
所以可重入锁需要一个重入计数变量,初始值设为0,当成功请求锁时加1,释放锁时减1,当释放锁之后计数为0则真正释放锁。重入锁还必须持有对锁持有者的引用,用以判断是否可以重入。
锁的公平性
如果锁能够严格按照线程请求锁的先后顺序分配锁,则认为锁具有公平性;如果某一线程能在其他等待线程之前获取到锁,则认为锁不具有公平性。
ReentrantLock
ReentrantLock
是JUC包里可重入的独占锁实现,它具有三个内部类:Sync、NonfairSync、FairSync
,通过构造函数的参数来指定锁是否是公平的,下面是一些核心代码:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1); // 这个1表示退出锁1次。
}
// 带超时限制的获取
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 其他代码省略
}
可以看到,ReentrantLock
都是把具体实现委托给内部类而不是直接继承自AbstractQueuedSynchronizer
,这样的好处是用户不会看到不需要的方法,也避免了用户错误地使用AbstractQueuedSynchronizer
的公开方法而导致错误。
ReentrantLock
的重入计数是使用AbstractQueuedSynchronizer
的state
属性的,state
大于0表示锁被占用、等于0表示空闲,小于0则是重入次数太多导致溢出了。
ReentrantLock.Sync
可重入锁内部实现的超类,主要实现了公平与非公平锁的共有方法,并提供了加锁操作的统一抽象:abstract void lock();
,还有核心的释放锁的操作。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 执行Lock.lock()方法,留给子类根据其公平性实现。
// 子类化的最主要原因是允许非公平的快速路径。
abstract void lock();
// 非公平获取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 锁是空闲的,进行加锁必须用CAS来确保即使有多个线程竞争锁也是安全的
if (compareAndSetState(0, acquires)) {
// 加锁成功
// 把当前线程设为锁的持有者,在获取前可用于判断是否是重入。
setExclusiveOwnerThread(current);
return true ;
}
}
else if (current == getExclusiveOwnerThread()) {
// 锁被占用且当前线程是锁的持有者,说明是重入。
int nextc = c + acquires;
if (nextc < 0)
// 溢出。加锁次数从0开始,加锁与释放操作是对称的,
// 所以绝不会是小于0值,小于0只能是溢出。
throw new Error("Maximum lock count exceeded");
// 锁被持有的情况下,只有持有者才能更新锁保护的资源,
// 所以这里不需要用CAS。
setState(nextc);
return true ;
}
return false ;
}
protected final boolean tryRelease(int releases) {
// 先读取state是为了获得一个读屏障,owner不是volatile的。
int c = getState() - releases;
// 只有锁的持有者才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {// 锁重入计数减到0,需要真正释放锁了。
free = true;
setExclusiveOwnerThread( null);
}
// 如果c为0,写操作完成后,其他线程就会看到锁被释放了,
// 所以setExclusiveOwnerThread必须在这个写之前完成。
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0; // 以state属性作为加锁次数。
}
final boolean isLocked() {
return getState() != 0; // 加锁次数为0表示没有被拥有
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
ReentrantLock.FairSync
提供公平性的锁实现。实现公平性的关键在于:如果锁被占用且当前线程不是持有者也不是等待队列的第一个,则进入等待队列。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1); //acquire会首先调用tryAcquire,所以公平策略的控制留给tryAcquire。
}
// tryAcquire的公平版本。除非是递归调用或没有等待者或者是第一个,否则不授予访问。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 读关卡
if (c == 0) {
// 锁空闲
if (!hasQueuedPredecessors() && // 没有等待结点,也就是第一个
compareAndSetState(0, acquires)) { // 尝试获取
setExclusiveOwnerThread(current); // 获取成功,设为持有者
return true ;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入
int nextc = c + acquires; // 重入次数增加
if (nextc < 0)// 溢出,重入次数太多,在改变状态之前抛出异常以确保锁的状态是正确的。
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true ;
}
return false ;
}
}
ReentrantLock.NonfairSync
提供非公平性的锁实现。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 执行lock。尝试立即闯入,失败就退回常规流程
final void lock() {
if (compareAndSetState(0, 1))// 首先进行获取
// 获取成功,把当前线程设为持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取失败,进入常规流程:acquire会首先调用tryAcquire,
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
可以注意到代码实现里的 acquire(1); sync.release(1);
,这里都是1,因为加锁与退出的计数步长的绝对值是必须一样的。可以把1换为2,但这样会减少锁可以重入的次数。
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。