转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72844011
1 前言
目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容。数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令。
在java中的locks包中的ReentrantLock、ReentrantReadWriteLock,还有同步包中的Semaphore、CountDownLatch类均是借助AQS同步器实现。
AQS的功能可以分为两类:独占锁和共享锁。它的所有子类中,要么实现并使用了它独占锁的API,要么使用了共享锁的API,而不会同时使用两套API,即便是它最有名的子类ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套API来实现的
2 概要
AQS类中,有两件重要的事情,即获取锁和释放锁。AQS类将其分为独占模式和共享模式
- 独占模式:其他线程试图获取该锁将无法取得成功
- 共享模式下:多个线程获取某个锁可能(但不是一定)会获得成功
// 独占模式,获取锁的入口
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 共享模式,释放锁的入口
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 共享模式,获取锁的入口
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 共享模式,释放锁的入口
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
// 以下四个方法需要子类实现
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
上面是AQS类的几个重要方法,其中tryAcquire(int)、tryRelease(int)、tryAcquireShared(int)、tryReleaseShared(int)四个方法是需要子类实现的。
3 存储结构
在AQS中,被阻塞的线程被包装成一个Node结构,而且阻塞的线程(Node)被存储在一个双向链表当中,如下图所示
+------+ prev +-------+ prev +------+
| Node | <---- | Node | <---- | Node |
head |thread| |thread | |thread| tail
| ws | ----> | ws | ----> | ws |
+------+ next +-------+ +------+
static final class Node {
// 模式,分为共享与独占
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 结点状态
// CANCELLED,值为1,表示当前的线程被取消
// SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
// CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
// PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
// 值为0,表示当前节点在sync队列中,等待着获取锁
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 结点状态
volatile int waitStatus;
// 前驱结点
volatile Node prev;
// 后继结点
volatile Node next;
// 结点所对应的线程
volatile Thread thread;
// 下一个等待者
Node nextWaiter;
}
4 源码分析
属性域
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
// 头结点
private transient volatile Node head;
// 尾结点
private transient volatile Node tail;
// 状态,0代表没有线程获取该锁,>0代表线程重入的次数
private volatile int state;
}
AQS获取锁的比较复杂,下面以三个线程A、B为例说明
Step 1
线程A通过acquire
获得锁,并且线程A执行,此时状态如图
Step 2
线程B通过acquire
获得锁,过程如代码注释,中间状态如下图所示。
public final void acquire(int arg) {
// tryAcquire返回false
if (!tryAcquire(arg) &&
// 先调用addWaiter
// 再调用acquireQueued
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
// 新生成一个结点,默认为独占模式
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 尾结点不为空,即已经被初始化
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列
enq(node);
return node;
}
// 加入队列
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 尾结点为空,即还没被初始化
if (t == null) {
// 头结点为空,并设置头结点为新生成的结点
if (compareAndSetHead(new Node()))
tail = head;
} else { // 尾结点不为空,即已经被初始化过
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
Step 3
由enq函数的代码可知,其实enq是自旋的,上面展示了enq执行第一次循环时候的状态,接下来循环第二次,并返回给acquireQueued,下面来看看acquireQueued。
//判断在等待队列中的线程是否中断
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋,每次循环尝试获得锁,如果尝试不成功后会挂起线程
// 等待release唤起
for (;;) {
final Node p = node.predecessor();
// 如果前驱节点是head节点,则尝试再获得锁,因为锁可能已经释放
// 如果获取锁成功
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱节点已经设置为release时通知它,故可以“停靠”在此节点后面
return true;
if (ws > 0) {
// 前驱节点已经cancel,这个时候我们会除掉这个节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 如果都不是,通过CAS操作将这个前驱节点设置成SIGHNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
注意:此时head节点的waitStatus已经被更新为-1,但NodeB在逻辑上并为成功park在head节点后。
Step 4
acquireQueued
是自旋的,Step 3中已经把前驱节点的状态设置为-1,即设置为通知状态,此时自旋重复上述Step 3,加入tryAcquire
仍为false,即获取不了锁,此时调用shouldParkAfterFailedAcquire
返回true,该NodeB在逻辑上成功park在head节点后,然后调用parkAndCheckInterrupt
挂起
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
Step 5
等待线程A执行完成了,调用release
函数释放锁,实际上调用tryRelease
,是AQS的state
减1,如果返回true,则成功释放。接下来调用unparkSuccessor
唤起等待线程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
// 把node的状态变为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 查找下一个状态-1的节点,即需要唤起的node
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;
}
// 调用unpark唤起
if (s != null)
LockSupport.unpark(s.thread);
}
5 总结
(1)线程被包装成Node,被保存在双向链表,同时有一个head和tail辅助指针。同时Node中还有waitStatus状态变量,只有一个Node的状态是-1时,才能停靠一个后续Node。
(2)head指针指向一个Node时,会将这个Node的状态设置为0,可以理解为当前获得锁的Node。
参考
http://blog.csdn.net/pfnie/article/details/53191892
http://www.jianshu.com/p/d8eeb31bee5c
转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72844011