ReentrantLock源码解析
ReentrantLock中使用了AbstractQueuedSynchronizer也就是AQS,完成了锁的获取和释放等操作。
下面来通过介绍ReentrantLock源码来理解,实现原理。
ReentrantLock的构造方法:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync(); // 默认选择非公平锁
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ?
new FairSync() // 非平锁
:
new NonfairSync(); // 公平锁
}
这里提到了两个内部类:
- new FairSync() ;
- new NonfairSync();
NonfairSync和FairSync的主要区别:
FairSync:先判断锁是否被其他线程调用,如果是则进入队列尾部等待;
NonfairSync:会首先尝试获取锁,获取失败后,会加入队列进入等待状态。
由于ReentrantLock的默认构造方法使用的是非公平锁(和synchronize一样默认为非公平锁 ,公平锁即线程是以请求锁的顺序来获取锁,非公平锁获取锁的顺序是随机的,可能有的线程永远获取不到锁),所以这里对NonfairSync进行解读。
lock方法:
判断线程是否获取过锁,如果是第一次state为0(state是一个volatile变量,表示获取锁的次数),执行setExclusiveOwnerThread方法,设置当前线程为锁的持有者。
如果不是第一次获取锁state > 1,表示重入锁的次数。
//获取锁操作
final void lock() {
//判断是否获取过锁,通过CAS操作对锁进行设置
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
注意:
AQS中的锁是可重入的,即同一个线程可以多次获取锁。
AQS中的锁也是独占的,即一个线程没有释放锁时,其他的线程不能获取锁。
如果第二次调用lock()获取锁,由于内存中state不为0,cas失败,则进入else执行acquire(1);
acquire:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire是一个抽象方法,NonfairSync中的实现如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 得到state数量
int c = getState();
// 没有获取过锁
if (c == 0) {
// 获取一次,cas设置state为acquires,也就是1
if (compareAndSetState(0, acquires)) {
// 设置拥有者线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 增加重入次数
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state,这里不需要同步,因为已经是统一线程第二次获取锁,也是在本线程里获取锁,没有线程安全问题,第一次调用的state和ownerThread一定是可见的
setState(nextc);
return true;
}
return false;
}
acquireQueued:
本方法中,判断了当前节点的前一个节点是否是head节点,如果是的话,则node节点是第二个节点,可以获取锁,获取锁成功,则设置头节点为node,消除了原来初始化产生的冗余节点,并且返回中断状态为false。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // 判断当前节点的 前驱节点 是否为队列头部 如果是 再次尝试上锁(如果头部节点 已经释放所, 则使当前线程成为持有者 并且设置自己为 头部。 同时释放前驱节点)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt() // 进入等待状态 等待唤醒
)
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Node(Thread thread, Node mode) { // Used by addWaiter
// Node = null
this.nextWaiter = mode;
// thread = Thread.currentThread();
this.thread = thread;
}
addWaiter方法中主要作用是在当前AQS维护的队列尾部,添加一个节点,如果队列为null,则需要创建一个冗余节点head,否则在队列尾部加入封装了新创建线程的Node,完成双向链表的节点添加。
addWaiter:
private Node addWaiter(Node mode) {
// mode=Node.EXCLUSIVE,排他模式 Node = null
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 获取AQS的尾节点
Node pred = tail;
// 尾节点不为null,在后方插入新的node,也就是null节点
if (pred != null) {
node.prev = pred;
// 设置AQS的tail为node节点
if (compareAndSetTail(pred, node)) {
// 连接上一个尾节点和node
pred.next = node;
// 返回新建的节点,封装了本线程
return node;
}
}
// 尾节点为null,则在队列中加入本节点
enq(node);
return node;
}
enq:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 如果其他线程在之前往队列中添加了节点,则tail可能不为null,需要再次判断
if (t == null) { // Must initialize
// 尾节点为null则需要新建Node,cas设置头节点为node,head为冗余节点
if (compareAndSetHead(new Node()))
// 设置尾节点为head
tail = head;
} else {
// 在队列后面添加节点,完成双向链表添加节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}