概述
什么是重入锁
重入锁ReentrantLock是显示锁的一种,是Lock接口最常见的实现,采取独占式资源获取方式,即一条线程持有资源锁时,其他线程必须阻塞直到线程释放锁,重入锁的特点是同一个线程可以多次获取锁。
重入锁基于队列同步器AQS实现,同时支持Condition多条件队列(等待队列),在实现了AQS的基础上,提供了公平和非公平两种锁的获取方式。
ReentrantLock源码解析
成员变量与构造方法
首先看一下ReentrantLock除了serialVersionUID以外唯一的成员变量:
private final Sync sync;
该成员变量是提供所有实现机制的同步器,下文会详析Sync类,围绕该变量,衍生了下列构造方法:
public ReentrantLock() {
sync = new NonfairSync();
}
/** FairSync类和NonfairSync类分别对应公平锁和非公平锁的实现 */
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
从构造方法可见,重入锁内默认实现的是非公平锁,可以通过传入参数改实现为公平锁
公平锁与非公平锁
此处简述公平锁与非公平锁的区别与特点:
- 非公平锁:当多个线程需要获取锁的时候,线程直接尝试获取,不会管队列中其他线程等待了多久,即无序获取,这样可能引发被称为饥饿的情况,即等待获取资源很久的线程仍不能获取锁。
- 公平锁:当线程需要获取锁时,直接加入队列队尾,每当有锁被释放,总是队列中等待时间最长的线程获取锁,即队首获取锁,保证了有序获取,这样能够减少饥饿情况发生的概率。
虽然公平锁可以有效减少饥饿的发生,但公平锁的效率往往没有非公平锁高,非特殊需求应采用非公平锁以保证效率,也许正因如此重入锁的无参构造默认实例化的是非公平锁的实现。
锁的实现
AQS为各类同步工具提供了基本框架,各同步工具主要负责实现tryAcquire等方法以及对资源状态state的实际逻辑实现,下文中诸如acquireQueued的方法以及资源状态state的详解还请移步专栏内AQS文章。
静态内部类Sync
Sync直接继承队列同步器AQS,内部主要实现了非公平获取锁和释放锁的方法,解析见注释部分,非重要代码省略,源码如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
/** 此方法就是非公平尝试获取锁的具体实现 */
/** 参数acquires为线程想要获取的资源(锁)数量 */
final boolean nonfairTryAcquire(int acquires) {
/** 首先获取当前线程 */
final Thread current = Thread.currentThread();
/** 获取当前状态 */
int c = getState();
/** 为0时表示当前没有线程持有锁 */
if (c == 0) {
/** CAS原子修改state的值 */
if (compareAndSetState(0, acquires)) {
/** 设置当前线程拥有独占访问权限 */
setExclusiveOwnerThread(current);
return true;
}
}
/** 判断当前线程是否持有锁 */
else if (current == getExclusiveOwnerThread()) {
/**
* 如果是当前线程持有锁
* 就将state原值加上acquires作为新值 表示当前线程持有锁的数量
*/
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
/** 判断当前线程是否持有锁 */
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
/** 创建一个新的Condition条件队列 */
final ConditionObject newCondition() {
return new ConditionObject();
}
}
非公平锁NonfairSync
static final class NonfairSync extends Sync {
/** 重写Lock的lock方法,使用时获取锁实际调用的就是该方法 */
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
/** 此处直接调用Sync中实现的获取资源方式即可 不需要重写 */
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
AQS中的acquire方法如下
public final void acquire(int arg) {
/** 执行一次获取锁的尝试 在非公平实现下也就是调用Sync的nonfairTryAcquire() */
if (!tryAcquire(arg) &&
/**
* 实际获取锁的操作都在acquireQueued方法内实现
* addWaiter方法用来向同步队列中添加封装了当前线程的节点
* 均非本文讨论重点 不展开叙述
*/
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
公平锁FairSync
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
/** 公平版本的锁获取尝试 覆盖重写Sync中的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;
}
}
hasQueuedPredecessors()方法的作用是判断是否有线程等待时间比当前线程更长,该方法是公平锁与非公平锁实现差异的体现,源码如下:
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
/**
* 先看第一个判断 如果h==t时 说明等待队列中一定只有一个节点
* 即只有正在尝试获取锁的节点 直接返回false
* 否则进入第二个判断
* 只有当同步队列的头节点有后继节点 且该后继节点封装的线程是当前线程时
* 返回false 因为同步队列的第二个节点一定是同步队列中等待时间最长且未持有锁的线程
* 其余情况均返回true 即当前线程前有比该线程等待时间更久的线程
*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
以上便是本篇文章的全部内容,建议搭配AQS和Condition文章食用
作者才疏学浅,如文中出现纰漏,还望指正