ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁 可以保证线程安全。而且它具有比synchronized更多的特性,比如它支持手动加锁与解锁,支持加锁的公平性。
AQS具备特性
AQS内部维护属性volatile int state (32位)
State三种访问方式
getState()、setState()、compareAndSetState()
AQS定义两种资源共享方式
AQS定义两种队列
lock()
final void lock() {
acquire(1);//关键函数
}
acquire()
public final void acquire(int arg) {
// tryAcquire(arg) 尝试获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()
//获取当前线程
final Thread current = Thread.currentThread();
//获取状态数
int c = getState();
//如果是0 说明当前没有线程持锁
if (c == 0) {
//队列中没有Node
if (!hasQueuedPredecessors() &&
//CAS方式 设置状态值为 acquires
compareAndSetState(0, acquires)) {
//设置锁的线程是当前线程
setExclusiveOwnerThread(current);
return true;
}
} // 如果不为0 ,且是当前线程是获取锁的线程,可重入。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
addWaiter(Node.EXCLUSIVE) // 由于没有获得锁,添加Node到队列
private Node addWaiter(Node mode) {
//建立节点
Node node = new Node(Thread.currentThread(), mode);
//获取尾巴节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS 尝试添加节点
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;
}
}
}
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//阻塞函数
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)) {
//如果获取到锁,将当前节点设置为head节点,让原来的head 被gc。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// p 是当前节点前一个节点。
/*shouldParkAfterFailedAcquire(p, node) 这个函数是利
用CAS自旋将pred的WaitStatus 设置为-1.
*/
if (shouldParkAfterFailedAcquire(p, node) &&
//阻塞
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
unlock()
public void unlock() {
sync.release(1);//关键函数
}
release()
public final boolean release(int arg) {
//尝试解锁,因为重入锁,所以要stack=0 才可以完全解锁
if (tryRelease(arg)) {
//如果解锁成功,队列不为空,而且真实首节点状态要为-1
Node h = head;
if (h != null && h.waitStatus != 0)
//接触阻塞状态
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor()
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
/*这里会尝试去将head的waitStatus的状态设置为0.个人感觉这个对这个是
没有关系。因为如果后面会将这个节点抛弃或者重新设置-1。共享锁可能会使
用,后面继续看源码。
*/
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//如果这个节点是CANCELLED 那么剔除这个节点。并且将唤醒几乎给下一个可唤醒的节点。
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;
}
//如果队列中还有真实线程Node 可以将其唤醒。
if (s != null)
LockSupport.unpark(s.thread);
}
lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
acquireInterruptibly(int arg)
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//如果线程是被中断不是队列首节点唤醒,抛异常 不会放入到CLH队列中去
if (Thread.interrupted())
throw new InterruptedException();
//若不是被中的 那么尝试获得CHL锁。
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
doAcquireInterruptibly()
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
//创建一个CLH节点并添加到CHL队列中去
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
//自旋
for (;;) {
//获取前一个节点,判断是不是Head之后的节点,也就是第一个可用节点
final Node p = node.predecessor();
//如果是首节点,尝试获取锁
if (p == head && tryAcquire(arg)) {
//成功,设置这个点为head,
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
//失败
//设置这个点为等待唤醒的点,并且静如等待状态,后续被唤醒后如果是中断唤醒的,就抛出异常。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
//如果是中断唤醒的那么会将这个节点删除。并将异常抛出
cancelAcquire(node);
}
}
cancelAcquire()
private void cancelAcquire(Node node) {
// 将这个点以及,之前的被标记的CANCELLED 标志的点都会被CHl队列清除出去。
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
//一直往前找,直到不是CANCELLED标记的Node
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//将这个点设置为CANCELLED
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
//如果这个节点是尾,将pred后面的都剔除CHL队列
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//如果不是尾,也不是首节点 也剔除,
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//首节点 在unparkSuccessor 也会剔除
unparkSuccessor(node);
}
node.next = node; // help GC
}
}