# ReentrantLock源码阅读

发布时间 2023-12-27 21:00:00作者: NOSAE

ReentrantLock源码阅读

本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏甚至是谬误,欢迎指出共同学习

本文基于corretto-17.0.9源码,参考本文时请打开相应的源码对照,否则你会不知道我在说什么

简介

ReentrantLock是可重入独占锁,所谓可重入指的是占有锁的线程继续在这个锁上调用lock直接加锁成功,当然,lock与unlock的调用次数最终数量要相等,否则不会释放锁。而不可重入锁则是lock成功后再lock就会被阻塞。ReentrantLock基于AQS(AbstractQueuedSynchronizer),详情请看AbstractQueuedSynchronizer源码阅读

例子

ReentrantLock使用比较简单,随便看看就行,比如实现一个线程安全的计数器:

public class Counter {
  private final Lock lock = new ReentrantLock();
  private int count;

  public void add(int n) {
    lock.lock();
    try {
      count += n;
    } finally {
      lock.unlock();
    }
  }
}

代码分析

类似在AQS一文中实现的Mutex,ReentrantLock的方法基本也是通过AQS的方法实现的,因此直接看基于AQS的私有静态内部子类Sync如何实现的。另外需要明确是的,ReentrantLock是独占锁,因此可以忽略共享模式的相关方法,如tryAcquire

ReentrantLock支持公平锁和非公平锁。至于公平与否的概念,详情可见我AQS源码阅读的文章,我定义了三个公平级别,ReentrantLock支持的公平锁为强公平级别,支持的非公平锁为于弱公平级别。

ReentrantLock的内部静态类FairSync和NonfairSync分别用于支持公平和非公平锁,两者都从Sync类派生而来,因此先介绍Sync这个类的核心方法。

Sync.tryLock

final boolean tryLock() {
  Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  } else if (getExclusiveOwnerThread() == current) {
    if (++c < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(c);
    return true;
  }
  return false;
}

tryLock功能是尝试加锁,返回加锁是否成功,成功的话重入次数+1,另外这个tryLock是非公平的,因为它没有检测等待队列是否有线程在等待,而是直接尝试修改state加锁。

读过AQS那篇的话,这个方法读起来可谓是毫无压力的,ReentrantLock其他方法读起来同样也很简单(即使没仔细研究过AQS读这个其实也不难)。要注意的是AQS.state的类型是int,一直lock而不unlock的话会造成int溢出,直接抛一个Error。

Sync.initialTryLock

abstract boolean initialTryLock();

initialTryLock的功能同样是尝试加锁,在lock中的开头调用一次。由于公平与否的区别在于尝试加锁前有没有检查等待队列,因此提取这个方法留给子类实现。后面会介绍实现。

Sync.lock

final void lock() {
  if (!initialTryLock())
    acquire(1);
}

lock的功能就是字面意思:加锁。实现很简单,尝试加锁失败就通过acquire获取锁。

Sync.tryRelease

protected final boolean tryRelease(int releases) {
  int c = getState() - releases;
  if (getExclusiveOwnerThread() != Thread.currentThread())
    throw new IllegalMonitorStateException();
  boolean free = (c == 0);
  if (free)
    setExclusiveOwnerThread(null);
  setState(c);
  return free;
}

tryRelease是重写AQS的方法,功能就是尝试释放锁。如果不是持有锁的线程尝试释放锁则直接抛异常,否则就将state(重入数)减去相应的次数。

上面几个就是Sync的核心方法。还记得AQS实现独占锁要实现的三个方法吗?tryAcquiretryReleaseisHeldExclusively(这个实现很简单,不展示了),其中tryAcquire同样因为公平与否的特性,交给子类实现。综上,子类需要实现tryAcquireinitialTryLock两个方法:

NonFairSync.initialTryLock

final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    if (compareAndSetState(0, 1)) { // first attempt is unguarded
        setExclusiveOwnerThread(current);
        return true;
    } else if (getExclusiveOwnerThread() == current) {
        int c = getState() + 1;
        if (c < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    } else
        return false;
}

嗯,看着有点眼熟呢,是的,跟Sync.tryLock几乎毛区别没有...不知道为什么不直接调用tryLock

NonFairSync.tryAcquire

protected final boolean tryAcquire(int acquires) {
  if (getState() == 0 && compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
  }
  return false;
}

也非常简单呢,不过注意lock和acquire的区别,lock可能是让state从0到1,从1到2...acquire就是纯纯的从0到1,也就是acquire的语义是本来线程是不占有锁的,现在要用acquire获取到锁,所以是从0到1。这里个函数很简单,但在这啰嗦了点,算是闲来无事突发的一些感悟。

FairSync.initialTryLock

final boolean initialTryLock() {
  Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  } else if (getExclusiveOwnerThread() == current) {
    if (++c < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(c);
    return true;
  }
  return false;
}

这个函数与NonFairSync.initialTryLock的区别只有:state从0到1之前先检查一下有没有正在等待队列的线程,以实现强公平。

FairSync.tryAcquire

protected final boolean tryAcquire(int acquires) {
  if (getState() == 0 && !hasQueuedPredecessors() &&
    compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
  }
  return false;
}

这个函数与NonFairSync.tryAcquire的区别只有:state从0到1之前先检查有没有比当前线程等待更久的线程。因为tryAcquire可能会在好几个时间点调用(线程进入等待队列之前,线程进入等待队列之后),所以使用hasQueuedPredecessors来检查,这个函数在以下两种情况下返回true:

  • 当前线程没进入阻塞队列,且队列不为空
  • 当前线程已经在阻塞队列中,但前面有更靠近队头(等待时间更长)的线程

这两种情况下都得获取锁失败,以实现强公平。

至于ReentrantLock其他的方法实在没必要解析了,比如:

public void lock() { sync.lock(); }
public boolean tryLock() { return sync.tryLock(); }
public boolean hasWaiters(Condition condition) {
  if (condition == null)
    throw new NullPointerException();
  if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
    throw new IllegalArgumentException("not owner");
  return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

是吧...

参考链接

「博客园」AbstractQueuedSynchronizer源码阅读