上一次写独占锁的获取,写到当前线程被挂起之后就结束了,那么什么时候才能唤醒这个线程呢?这次来分析独占锁的释放。JAVA中的内置锁在线程退出临界区时会自动释放锁,而ReentrantLock之类的显示锁,需要手动释放,一定要写到finally代码块中!!
因为锁的释放没有公平与非公平之说,所以直接写在了AQS中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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;
//如果头节点不为null,并且waitStatus不是最初初始化时的状态
if (h != null && h.waitStatus != 0)
//唤醒下一个节点
unparkSuccessor(h);
return true;
}
return false;
}

源码看起来并不复杂,主要由tryRelease(arg)和unparkSuccessor(h)实现。

tryRelease(int releases)

ReentrantLock中的Sync内部类实现了AQS接口,所以也就实现了相应的tryRelease方法,源码中没有用到CAS等技术,是因为release方法的执行一定是获取锁之后执行的,再重点提示一下,一定要在finally代码块中释放锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

unparkSuccessor(Node node)

目的就是唤醒离头节点最近的节点线程,从后向前迭代,在分析节点插入时,因为不是原子操作,所以可能会有尾节点分叉现象,所以从后往前找更合适。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
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;
//ws:CANCELLED=1 ,初始化时等于0,但是进入这个方法前确定ws不等于0,
// 所以只要ws不为CANCELLED,就将ws置为0
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;
//后继节点为null或者后继节点ws为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;
}
//找到了需要被唤醒的节点
if (s != null)
//唤醒节点所标识的线程
LockSupport.unpark(s.thread);
}

执行到 LockSupport.unpark(s.thread);这一步时,唤醒了那个阻塞的线程,就回到了上一篇文章中的parkAndCheckInterrupt方法中,LockSupport.park(this)被打断唤醒,所以向下执行,获取线程中断状态,并且清空中断状态。

1
2
3
4
5
6
7
8
9
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

之后返回上一层代码acquireQueued(final Node node, int arg):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}
}

如源码,这里保留了中断状态,继续循环,当获取锁之后,返回了中断状态,继续返回上一层代码acquire(int arg),独占锁的获取逻辑:

1
2
3
4
5
6
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//就看这里!!!
selfInterrupt();
}

如上,执行到了 selfInterrupt()方法:

1
2
3
4
5
6
7
/**
* Convenience method to interrupt current thread.
*/
static void selfInterrupt() {
//中断当前线程
Thread.currentThread().interrupt();
}

中断了当前线程,当然,只是标志一下当前线程是中断的,纵观独占锁的获取与释放可以看出,AQS只是将中断延后处理,并没有响应中断。