一. 锁的释放
上一节中,我们讲完了加锁的过程,本节我们先来讲锁是如何释放的,还是以我们上节的那个最简单的例子,使用unlock解锁,这里有一个常识,unlock需要在finally语句块中,不然抛异常了,就会导致死锁。
public class Test {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
try {
reentrantLock.lock();
System.out.println("XXXXXXXXXXX");
}finally {
reentrantLock.unlock();
}
}
}
点开unlock方法,还是那个sync,调用了父类AQS的release方法,同加锁差不多,首先调用tryRelease方法去尝试释放锁,由Sync类实现
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
首先获取到state的值,state的值 - 1代表了锁被多少次重入了,因此此时使用state - releases表示,当前线程释放后还需不需要再次unlock,直到state被减至0后,才能表示所有重入的锁都被释放了,这是其他线程才能加锁成功。
首先要判断当前锁跟持有锁的线程是否是同一线程,如果不是则直接抛出异常,因此我们不能通过另起一个线程的方式来释放掉锁,只能按照排队的方式由当前线程来释放。
当state减成0的时候,表示当前锁可以被释放掉了,此时设置free=true,持有锁的线程置为null
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;
}
- 如果放回false,表示当前锁有重入,需要再次调用unlock来释放,那我们直接unlock,不lock会发生什么呢?不lock一下exclusiveOwnerThread就会为null,Thread.currentThread() != getExclusiveOwnerThread()条件通过,抛出异常。因此lock N次就必须要unlock N次,lock个数大于unlock个数导致死锁,unlock大于lock抛异常。
- 如果tryRelease返回true,判断头节点的状态是否不为0
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
这里只判断头节点是因为只有头节点的下一个节点才能执行到unlock操作,因此只需要判断head节点的waitStatus是否为-1,即Node.SIGNAL,由于在加锁的时候我们已经把所有等待节点的上一个节点的waitStatus的状态变为了-1,因此这里ws<0肯定是满足的,故而把head节点的waitStatus置为0
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);
}
此时各节点的状态将会是如下这样
这是拿到head的下一个节点node2,条件不满足,这里要满足的话涉及到锁的取消,而锁的取消就涉及到我们上一节中没有讲到的一个方法,cancelAcquire,这个方法只有在我们使用lockInterruptibly()加锁时才会触犯,这个我们后面再讲,而这里就是从尾部去递归查找离head最近的一个没有被取消的锁进行释放
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;
}
这里我们肯定找到的是头节点的下一个节点,对它进行释放,释放完成后,还记得我们上一节中讲的那个阻塞吗?parkAndCheckInterrupt()中调用的LockSupport.park(this);这里就会被解开,继续进行下一次for循环,去获取锁,在没有了其他线程竞争的前提下,这里就一定会获取到锁,当前节点编程head节点,原head节点失去引用,随后被GC回收。
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);
}
}
如果假设此时只有这一次节点,没有node3,那么tail和head节点都将指向这一个。
二. 锁的取消
OK,释放锁至此就走完了,下面我们来看一下刚才提到的cancelAcquire方法,锁的取消并没有提供开放的API来给程序员调用,而是当我们调用lockInterruptibly方法时,这个方法最终逻辑由AQS实现,区别在于该方法实现的锁是可以被打断的,以下是main方法的调用。
public class Test {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
try {
// reentrantLock.lock();
reentrantLock.lockInterruptibly();
System.out.println("XXXXXXXXXXX");
}catch(InterruptedException e){
}finally {
reentrantLock.unlock();
}
}
}
基本思路与lock中的acquireQueued方法差不多,唯一的区别在于这个方法会在parkAndCheckInterrupt方法检测到当前线程被其他线程打断之后,会直接抛出异常,然后会进入finally的逻辑,执行cancelAcquire方法
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
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) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
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(node);
}
node.next = node; // help GC
}
}
- 假设我们已经有4个线程在这里等待锁
- 此时node3线程被一个线程打断了,进入到了cancelAcquire方法,首先把持有的线程thread置为null
- 然后判断当前线程的上一个node节点的waitStatus是否大于0,如果大于零,则表示上一个也是被取消了的,整个链就会跳过上一个
- Node predNext = pred.next此处拿到当前node本身
- 把当前node的waiStatus置为1
- 判断node3是否已经是最后一个node,如果是就把上面循环获取到的第一个非取消状态的node2赋值给tail,并且把node2的next置为null,简单来说就是把尾部的自己从链路删除,此处node3的情况不满足,走else逻辑
- else判断中如果node2不是head节点,并且它的waitStatus=-1,并且node2持有的线程不为null,满足,然后把node2的不为空且在等待锁的next指向node4,next已经跳过了node3
此时node3被打断后,就会走到走到finally中的unlock方法,而如果判断当前线程与持有锁的线程时就会抛出IllegalMonitorStateException异常
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;
}
而如果node2也被打断了的时候,调用cancelAcquire方法,这时会通过将node2的waitStatus置为0,thread置为null,然后走到else的unparkSuccessor方法,将node2的next节点unpark
else {
unparkSuccessor(node);
}
根据上面的图可以看到node2的next现在是node4.
node4被唤醒后,会继续进入doAcquireInterruptibly方法的死循环,判断p的上一个节点是否是头节点,很明显,node4的上一个节点还是node3.
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
因此进入shouldParkAfterFailedAcquire(p, node)判断,传入pred【node3】,node【node4】,
判断到node3的waiStatus>0了,因此递归循环递归,发现node3和node2的waitStatus都大于0,直到head节点node1,此时仍在阻塞,waitStatus=-1,因此将node4的prev指向node1,node1的next指向node4,于是完成节点跳过了取消的节点.
这个循环比较难理解,首先是pred=pred.prev,这个意思就是把node2直接赋值给node3,因此原来的node3的变量直接指向了node2,再执行node.prev=pred,意思是把node4的prev指向node2,因此node3就永远失去了引用,再循环遍历一次,node4的prev就指向了node1,最后再把node1的next指向node4,node2也失去了引用,最后被GC回收。最终形成的结构如下图:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// 这一段理解起来
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
跳过后再次进入doAcquireInterruptibly循环,尝试获取锁,如果此时node1的线程还没有执行完成,node4将再次park起来等待。
以上就是cancelAcquire的基本流程,很多地方因为是并发所以不好测试,我这里有一份测试方法,可以用来参考一下。
public class Test {
private static ReentrantLock reentrantLock = new ReentrantLock(true);
public static Map<Integer, Thread> map = new ConcurrentHashMap<Integer, Thread>();
public static void main(String[] args) {
for(int i = 0; i < 20; i++){
final int fi = i;
new Thread(new Runnable() {
public void run() {
try {
Thread t = Thread.currentThread();
Thread.sleep(fi * 1000);
System.out.println(fi + ":" + t.getName());
map.put(fi, t);
reentrantLock.lockInterruptibly();
Thread.sleep(500000000);
System.out.println("XXXXXXXXXXX");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}).start();
}
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String commond = scanner.nextLine();
Thread thread = map.get(Integer.valueOf(commond));
thread.interrupt();
}
}
}