ReentrantLock
此篇是接上章 Java AQS学习 之 ReentrantLock源码解析(一)的内容。
Demo源码
那么现在继续解析相关ReentrantLock的解锁(unlock)操作。
以下是Demo源码:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.System.out;
public class ReentrantLockDemo1 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock(true);
Thread thread1 = new Thread(() -> {
out.println("thread1 第一次进行加锁操作");
reentrantLock.lock();
out.println("thread1 第一次加锁成功");
out.println("thread1 第二次次进行加锁操作");
reentrantLock.lock();
try {
out.println("thread1 第二次加锁成功");
TimeUnit.SECONDS.sleep(2);//设置占有锁2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println("thread1 释放锁资源");
reentrantLock.unlock();//第一次解锁
reentrantLock.unlock();//第二次解锁
});
thread1.start();
//确保线程先后顺序
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(() -> {
out.println("thread2 进行加锁操作");
reentrantLock.lock();//线程2进行锁占有操作
out.println("thread2 加锁成功");
reentrantLock.unlock();
out.println("thread2 释放锁资源");
});
thread2.start();
//确保线程先后顺序
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread3 = new Thread(() -> {
out.println("thread3 进行加锁操作");
reentrantLock.lock();
out.println("thread3 加锁成功");
reentrantLock.unlock();
out.println("thread3 释放锁资源");
});
thread3.start();
}
}
当前AQS状态
上篇讲到,当前执行到out.println(“thread3 进行加锁操作”); 这语句,并且阻塞在
==reentrantLock.lock();==这里。下图是当前AQS状态:
unlock解锁操作
- 14 接下来,当thread1执行完 TimeUnit.SECONDS.sleep(2);//设置占有锁2秒钟 时会进行 reentrantLock.unlock();//第一次解锁操作
/**
* Attempts to release this lock.
*/
public void unlock() {
sync.release(1);
}
- 15、 调用 sync.release(1) ,即调用AQS的release方法
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- 16、 然后继续调用 tryRelease(arg) 方法 ,此时会用ReentrantLock静态内部类Sync
(abstract static class Sync extends AbstractQueuedSynchronizer)的实现方法。
16.1 int c = getState()-relases; 即:c=2-1=1;。
16.2 if (Thread.currentThread() != getExclusiveOwnerThread())为false。
16.3 设置free=false,即先设置锁资源空闲状态为false。
16.4 if (c == 0) 为false 所以不会执行里面的代码块。
16.5执行setStatue( c ),状AQS的state设置为1,并且返回free==false。
16.6 tryRelease->release->unlock,此时unlock操作完成。
thread1对锁资源进行了一次解锁操作,但是因为前面thread1进行了两次加锁,所以锁资源仍被thread1占用。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())//在不进行lock的时候直接unlock会出现此错误
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
- 17、 接着会进行thread1第二次解锁操作unlock()->release(1)->tryRelease(1)
17.1 此时 int c = getState() - releases; c=1-1。
17.2 if (c == 0) 为true,那么 free = true;setExclusiveOwnerThread(null);
17.3 setState( c );则status被赋为0 ,并且return true。 - 18、tryRelease(1)return true则进入release方法中的if (tryRelease(arg)) 代码块中。
18.1 Node h = head; 变量h指向队首 ,根据入队可知,此时的head为无线程的节点。
18.2 if (h != null && h.waitStatus != 0)条件成立,会执行unparkSuccessor(h);
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- 19、 解析下unparkSuccessor(h)方法源码
19.1 int ws = node.waitStatus; 取出队首结点的waitStatus,因为根据前面入队操作可知,此时的ws为-1,即 if (ws < 0)成立,会执行里面的代码块
19.2 compareAndSetWaitStatus(node, ws, 0);改变节点状态,为0
19.3 Node s = node.next;将s指向队列的每二个节点,即是指向代表thread2的节点。
19.4 if (s == null || s.waitStatus > 0) 为false,不执行里面代码
19.5 if (s != null) 成立,执行 LockSupport.unpark(s.thread);语句,那么thread2会被唤醒。并且到此,unlock操作就全部完成了
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);
}
- 20、当线程thread2被唤醒后,会在原本被park的语句中继续下一步的执行
return Thread.interrupted(); Thread.interrupted()在其park期间若调用用了tread2.interrupt()则会返回true,否则false,本Demo并未调用interrupt方法,所返回false。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 20、parkAndCheckInterrupt返回false后,回到acquireQueued方法,
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())为flase,所以会接着执行下一遍for循环。
20.1 final Node p = node.predecessor();此时p即是队首head。
20.2 所 p == head 成立 会继续 tryAcquire(arg),因为此时status为0,即代表并无线程占用此锁资源,可成功占用,则返回true。
那么 if (p == head && tryAcquire(arg))成立,将执行里面代码块。
20.3 setHead(node);将队首指向第二节点,并且会将此节点的thread和prev属性置空。那么此节点则成为了队首。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
20.4 p.next = null; // help GC 此时将原本的队首的next属性,使得此节点满足GC回收条件。
20.5 failed = false; 表示 代表thread2的线程在其park期间并没有被取消。
20.6 return interrupted;此处的interrupted为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);
}
}
到此,AQS的队列状态如下图:
21、此时返回到acquire方法中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))为false,则if条件不成立,直接返回。那么到此,thread2加锁完成,会打印:“thread2 加锁成功”
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 22、接着,thread2进行解锁,与之前thread1解锁流程相同,在解锁后,同样会唤醒thread3,thread执行完lock和打印"thread3 加锁成功",然后进入unlock操作。
- 23、在执行到release方法if (h != null && h.waitStatus != 0)判断条件时,因为此时thread3后面已经没有节点,那么其waitStatus为0,所以不需要唤醒任何线程,直接返回true解锁成功。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
最终AQS状态
此时,AQS状态如下图:
非公平锁分析
24、以上ReentrantLock公平锁FairSync的加解锁流程,现在看下非公平锁NonfairSync有何不同,以下是NonfairSync的lock方法,和nonfairTryAcquire方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
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;
}
从以上源码可知,非公平锁的lock方法会首先发起抢占锁资源的命令,若抢不到再排队;而公平锁则是首先看是否要排队,若无线程在排队才去抢占,否则加入队列中。
总结
从加解锁流程分析可知,
- 1、AQS实现的原理主要是通过int status标识锁状态,0为未加锁状态,节点Node head和Node tail开成阻塞队列。
- 2、当线程占用锁资源成功(tryAcquire为true)时status会加1,可重入锁。
- 3、当status不为0时有线程进行加锁操作就会将其阻塞并入等待队列。
- 4、解锁时会将status减1,当status为0时会,查看是否有等待队列并且队列中有线程被阻塞,有则唤醒最先入队列的线程。
以上是个人关于ReentrantLock加解锁的操作已进行简单分析与理解,若有理解不正确的还请各路大神指正。
彩蛋
从源码可知,AQS实现的基础皆为LockSupport.park(thread)、LockSupport.unpark(thread)和compareAndSetXXX,而这此方法都是调用的sun.misc.Unsafe类里面的相关方法,后续将会对LockSupport和Unsafe进行深入研究。