概述
AQS是Java中除了synchronized锁,其他锁的基类,掌握了其原理,对我们了解其他锁事半功倍。
AQS通过volatile int state和FIFO队列,来实现我们期望的同步需求,子类通过继承同步器并需要实现它的方法来管理其状态,在多线程中对状态的操作必须保证原子性,因此需要用到如下方法
private volatile int state;
protected final int getState() {
return state;
}
//调用此方法来实现原子操作
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
有了保证原子性还不够还要保证同步,在AQS中通过Node对象来保证同步性,代码如下:
private transient volatile Node head;
private transient volatile Node tail;
/**
节点的状态。其中包含的状态有:
CANCELLED,值为1,表示当前的线程被取消;
SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
值为0,表示当前节点在sync队列中,等待着获取锁。
**/
static final class Node {
//前置节点
volatile Node prev;
// 后置节点
volatile Node next;
//入队列时的当前线程
volatile Thread thread;
//存储condition队列中的后继节点。先不要过段关注这个
Node nextWaiter;
}
对于锁的获取,请求的节点挂载在节点的尾部也就是tail,而锁的释放在获取是是从head开始的。
排它锁实现细节
要实现排它锁的必须实现两个接口,
方法名 | 描述 |
---|---|
protected boolean tryAcquire(int arg) | 排它锁的获取 |
protected boolean tryRelease(int arg) | 排它锁的释放 |
示例
package com.example.demo;
class Mutex implements Lock, java.io.Serializable {
private static class Sync extends AbstractQueuedSynchronizer {
//获取锁
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
//更改state的状态为1,如果更改成功,保存当前线程
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
//更改为0,因为这个地方没有锁的竞争,所以不用compareAndSetState
setState(0);
return true;
}
....
}
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
...
}
}
可以看到通过继承AQS实现一个独占锁还是很简单的。
实现分析
acquire方法
可以看到我们在获取锁的时候调用了lock方法,而lock方法又调用了acquire方法,让我们看看acquire实现细节
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以看到在acquire方法中首先调用tryAcquire方法进行锁的获取,如果获取成功直接退出,获取不成功则调用addWaiter产生了node,并作为参数传递给了acquireQueued方法。先分析下addWaiter
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//第一次进来这里为空,直接进入enq,否則 //把node插入到tail处,并node.prev为上一个tail,其次prev.next等于node
Node pred = tail;
if (pred != null) {
node.prev = pred;
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
//先创建head节点,并把head节点等于tail节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
//把node插入到tail处,并node.prev为上一个tail(如果是第一次创建,为head),其次prev.next等于node
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 第一次进来这里为空,直接进入enq,否則把node插入到tail处,并node.prev为上一个tail,其次prev.next等于node
- 如果tail节点为空,先创建head节点,并把head节点等于tail节点
- 把node插入到tail处,并node.prev为上一个tail(如果是第一次创建,为head),其次prev.next等于node
acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点上一个节点
final Node p = node.predecessor();
//如果上一个节点等于head,在此尝试获取锁,如果获取成功设置当前节点为头部节点。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果上一个节点不等于头节点或者是头节点但是没有获取成功,那么更改SIGNAL为-1,并对当前线程进行了park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 获取当前节点上一个节点
- 如果上一个节点等于head,在次尝试获取锁,(因为执行在这里的时候,head节点的锁已经释放,所以进行了在此获取)如果获取成功设置当前节点为头部节点。
- 如果上一个节点不等于头节点或者是头节点但是没有获取成功,那么更改SIGNAL为-1,并调用parkAndCheckInterrupt方法进行阻塞。
release方法
在unlock的时候,调用了release进行了锁的释放
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) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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);
}
- 调用tryRelease更新state状态
- 更改head的waitStatus为0,并调用unpack唤起当前节点的后续节点。
总结
加锁
- 调用compareAndSetState更改state状态,如果更改成功,那么获取锁成功
- 如果未更改成功,判断前驱节点是否为头结点,如果是尝试在次获取锁,如果获取成功那么设置为当前节点为头结点。
- 如果获取失败,当前线程进行休眠,等待唤醒。
释放锁
- 更改state状态,如果更改成功,调用unpack唤起后续节点。
参考
http://ifeve.com/introduce-abstractqueuedsynchronizer/