引言
ReentrantLock是jdk1.5版本引入的类,主要实现类Sync继承于AbstractQueuedSynchronizer简称为AQS,很多类的实现例如CountDownLatch以及Semaphore的底层都是它。以下是对AQS的简单学习。
CLH队列
首先要说说实现AQS的数据结构,是一个CLH同步队列队列,jdk中实现如下。
更详细一点的话,看下面这张图。
图片转自https://www.jianshu.com/p/da9d051dcc3d
其实从这里可以很明白的看出,其实现首先有一个head指针指向头结点,然后一个tail指针指向尾结点,然后是一个双向队列。而且head节点是不包含线程信息的,head->next是第一个阻塞节点。
继承图
图片转自https://blog.csdn.net/qq_38293564/article/details/80515718
lock流程(nonfair)
流程图大概如下
这里的下一个节点,是指该唤醒的下一个节点。
从ReentrantLock是一个互斥锁,所以先从不公平互斥模式开始,首先lock方法调用以下方法。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
compareAndSetstate这个方法是一个原子性方法,底层由unsafe类实现,通过自旋cas操作来确保原子性,当处于Nonfair下,首先当前线程会竞争锁,不成功调用AQS的acquire方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
还是先看一下tryAcquire方法,这个方法在aqs定义,在各自子类中具体实现。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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");
setState(nextc);
return true;
}
return false;
}
实际最终决定逻辑的是这个nonfairTryAcquire方法,可以看到这里首先state==0的时候会竞争锁,也就是说没有任何线程占用时如果竞争成功就持有锁返回true,else if 这里是可重入性是实现,如果是当前线程持有锁更新state并返回true,如果不是以上两种情况,说明竞争失败了, 要进入CLH队列中。
lock-加入CLH队列流程
如果竞争失败,会调用AQS的addWaiter方法加入队列。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
首先node节点封装当前线程信息,if(pred != null) 其实就是队列不为空时直接加到队尾操作,这里的入队操作没有线程安全问题,可以自己推推。如果队列为空,调用下面enq方法入队,if(t == null)队列为空时,进行初始化,然后再进行入队操作,其实下面的enq方法包含了入队的所有操作,这样代码会有点冗余,但是这样效率高,一直cas操作很吃资源。入队之后调用acquireQueued方法,这个方法主要是判断要不要阻塞自己。
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);
}
}
到这里入队操作就已经完成了,接下来看一下unlock具体实现的流程。
unlock流程(nonfair)
unlock首先调用的是release方法。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
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;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
同样这个tryRelease方法也是由子类具体实现,这个方法逻辑很简单,只有当前线程持有线程并且如果释放state==0就可以成功。并且从后往前找最后一个可以唤醒的线程,也就是离head最近并且可以唤醒的线程。
以上是非公平锁的lock与unlock流程。下面分析一下公平锁的。
lock( fair)
final void lock() {
acquire(1);
}
可以看到这里与非公平锁比较少了一次竞争操作。
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;
}
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
nonfair当 state ==0 时直接进行compareAndSetState(0, acquires)) 竞争操作,而公平锁还有一个!hasQueuedPredecessors()判断,从名字我们就可以知道,如果没有比它优先级更高的线程,它才会进行竞争操作。
其它流程与nonfair一致。
总结
其实nonfair与fair最大的区别就是刚开始nonfair就会进行一次cas操作来获得锁,失败后在tryAcquire并且state==0时又直接进行cas来获得锁,而公平锁在这里还要判断优先级,其它的操作基本都一致。通过学习也可以知道Reentrant是如何进行重入操作。
公平锁虽然保证了顺序,但却多了线程唤醒,阻塞一系列的上下文切换操作,所以它的效率和吞吐量来说是不如非公平锁,而且默认ReentarantLock是非公平锁,上文是互斥模式下的分析,共享主要是在ReentrantReadWriteLock读写锁的应用下,相比下读写分离,效率更好。
To live is to function --xxy专用小尾巴。