1、AQS属性:
以前文章提到过,看一个类的属性这个类整个的功能。
AQS中的属性:
public abstract class AbstractQueuedSynchronizer{
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
}
state取值:
//节点从同步队列中取消
int CANCELLED = 1
//后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;
int SIGNAL = -1
//当前节点进入等待队列中
int CONDITION = -2
//表示下一次共享式同步状态获取将会无条件传播下去
int PROPAGATE = -3
//初始状态
int INITIAL = 0;
AQS的静态内部类Node:
public abstract class AbstractQueuedSynchronizer{
static final class Node {
//标记表示节点正在共享模式中等待
static final Node SHARED = new Node();
//标记表示节点正在独占模式下等待
static final Node EXCLUSIVE = null;
//节点状态
volatile int waitStatus
//当前节点/线程的前驱节点
volatile Node prev
//当前节点/线程的后继节点
volatile Node next;
//加入同步队列的线程引用
volatile Thread thread;
//等待队列中的下一个节点(和Condition配合使用)
Node nextWaiter;
}
}
剩下的方法都是围绕以上属性所服务的:
AQS提供的主要方法
- getState():返回同步状态的当前值
- setState(int newState):设置当前同步状态
- compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性
- isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程独占
- acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由改方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法
- acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法抛出InterruptedException异常并返回
- acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态
- acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断
- release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒
- releaseShared(int arg):共享式释放同步状态
- tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态
- tryRelease(int arg):独占式释放同步状态
- tryAcquireNanos(int arg, long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true
- tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0表示获取成功,否则获取失败
- tryReleaseShared(int arg):共享式释放同步状态
- tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制
2、独占式锁
2.1、获取独占式锁
调用lock()方法是获取独占式锁,获取失败就将当前线程加入同步队列,成功则线程执行。而lock()方法实际上会调用AQS的acquire()方法
acquire根据当前获得同步状态成功与否做了两件事情:
- 成功,则方法结束返回
- 失败,则先调用addWaiter()将线程加入等待队列,然后在调用acquireQueued()方法:排队再次获取锁。
public final void acquire(int arg) { //获取同步状态成功,则直接返回。若失败则先调用addWaiter()方法再调用acquireQueued()方法 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } }
2.2、获取独占式锁态失败:做入队操作
private Node addWaiter(Node mode) {
// 1. 将当前线程构建成Node
Node node = new Node(Thread.currentThread(), mode);
// 2. 当前尾节点是否为null
Node pred = tail;
if (pred != null) {
// 当前同步队列尾节点不为null,则将当前节点尾插入的方式插入同步队列中
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 当前同步队列尾节点为null,说明当前线程是第一个加入同步队列进行等待的线程
enq(node);
return node;
}
private Node enq(final Node node) {
//自旋的过程(for (;;))
for (;;) {
Node t = tail;
if (t == null) {
//1. 构造头结点
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 2. CAS操作设置尾节点,CAS操作失败后负责自旋进行尝试会在for (;;)for死循环中不断尝试,直至成功return返回为止。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在同步队列中的节点(线程)会做什么事情了来保证自己能够有机会获得独占式锁了?自旋:acquireQueued
2.3、入队列后,线程排队获取独占式锁:acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前节点的先驱节点
final Node p = node.predecessor();
// 2.1 如果当前节点的先驱节点是头结点并且成功获取同步状态,即可以获得独占式锁
if (p == head && tryAcquire(arg)) {
//获取锁成功,出队操作(队列头指针用指向当前节点)
setHead(node);
//释放前驱节点
p.next = null;
failed = false;
return interrupted;
}
// 2.2 获取锁失败,线程进入等待状态,等待获取独占式锁
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果等待过程中被中断过,就将interrupted标记为true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//做出队操作:
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
//就进入waiting状态,直到被unpark()
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//拿到前驱的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
return true;
if (ws > 0) {
/*
* 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
* 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被GC回收
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//调用park()使线程进入waiting状态
LockSupport.park(this);
//如果被唤醒,查看自己是不是被中断的
return Thread.interrupted();
}
2.4、释放独占式锁: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;
}
//释放锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//唤醒等待队列里的下一个线程
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)//从这里可以看出,<=0的结点,都是还有效的结点。
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒
}