aqs 是什么
demo 示例:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class AqsDemo {
//银行办理业务来模拟aqs 如何进行线程管理
//三个线程模拟三个顾客,窗口只有一个
//debug 走一遍啊 a 阻塞的时候BC locK 的时候怎么走的
public static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println("A thread come in ");
try {
TimeUnit.MINUTES.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}, "A").start();
//B只能等着,因为只有一个窗口
new Thread(() -> {
lock.lock();
try {
System.out.println("B thread come in ");
} finally {
lock.unlock();
}
}, "B").start();
//C只能等着,因为只有一个窗口
new Thread(() -> {
lock.lock();
try {
System.out.println("C thread come in ");
} finally {
lock.unlock();
}
}, "C").start();
}
}
以ReentrantLock的非公平锁为例。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
A lock 的时候,因为没有其他线程抢占锁,所以走的是 if ,将当前线程设置为占用锁的线程,并将state 设置为1。
因为A 线程 睡眠了20分钟,所以B线程占有锁的时候,走的是else 。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire()这个方法的判断里面有三个方法,都符合的情况下,会中断当前线程。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
tryAcquire(arg) 走的是这段逻辑,首先获取当前线程B,获取state。state此时为1,当前占有锁的线程是A,所以直接返回false。(这里的else if 模块主要是为了实现可重入锁)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
addWaiter(Node.EXCLUSIVE)这个方法传入的Node为null,创建了一个节点,并设置节点的下一个节点是null,tail是CHL双端队列的尾节点,此时为null,走的是enq(),之后返回node
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq(Node node) 这个方法,一直自旋去入队,首先拿到tail 尾结点,此时为null,所以,初始化了一个新的节点,节点的线程为null,waitStatus=0,然后将首尾节点都指向它,这个节点我们称为哨兵节点(有些阻塞队列操作出队入队就不需要每次判断头节点是否为null了),所以CHL双端队列的第一个节点是哨兵节点。因为自旋,所以第二次进入的时候,此时tail 不为null了 ,因为它指向了哨兵队列了。此时走下面的逻辑,将新来的节点的前驱节点设置为tail(哨兵节点),然后将tail节点指向新来的节点(B线程的节点),设置成功以后,将哨兵节点的后记节点设置为新节点B,返回tail 节点(tail 节点此时指向B线程的节点,所以返回的就是B线程的节点)。
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);
}
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 将B节点拿到以后,自旋转去执行这段代码。
node.predecessor()是返回前驱节点,B的前驱节点是哨兵节点。判断它是不是头节点,明显是的,头结点指向哨兵节点,然后再去尝试获取锁,因为state=1,当前锁仍然被A 占用,所以尝试获取锁tryAcquire(arg)仍然返回false。所以不走上面的if,下面的if 是抢占锁失败的逻辑
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;
}
将前驱节点和当前节点传入,前驱节点的waitStatus =0 所以执行else ,将前驱节点的waitStatus设置为-1(意思是后继线程需要取消停放)。然后执行parkAndCheckInterrupt()这个方法,这个方法就是将B线程设置为等待,并返回当前线程是否为中断,明显不是,此时B线程就卡在这里,等待被唤醒(A线程解锁)
=========================================================================
以上是B线程来的时候,以下是C线程来的时候
=========================================================================
仍然是这三个方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
尝试获取锁,state=1,占有锁的线程是A,所以返回false,addWaiter因为tail 节点不是空节点了,指向的是B线程的节点所以走的是下面的逻辑,将新节点的前驱节点指向tail 节点(B节点),然后将新节点C设置为尾节点,并且将B节点的后续节点设置为新节点,最后返回C节点。
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
然后走 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 方法,和B线程来的时候一样,先抢占锁,拿不到就将前驱节点的waitStatus设置为-1,然后进入等待(等待锁的释放)。
上图是A线程占用锁,B,C入队的图。
=========================================================================
以下是A 解锁的逻辑
=========================================================================
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
unlock走的是这段代码。
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;
}
tryRelease将state -1 ,并且将当前线程(A线程不设置为占用锁的线程),返回true。判断头结点是否为null,state是不是不等于0,头结点就是哨兵节点,state=-1,所以 unparkSuccessor(h),然后返回true,A线程解锁成功。
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);
}
unparkSuccessor(h)将头结点传入,首先将waitStatus 设置为0,然后将头结点的下一个节点unpark,唤醒。
=========================================================================
以下是B 被唤醒的时候执行逻辑
=========================================================================
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);
}
}
之前说的,B,C在这里抢锁失败,进入等待。一旦B被唤醒,走第一个if,他的前驱节点是头结点,尝试去获取锁,获取到了,并将当前线程设置为占用锁的线程 ,因为state =0 。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
占用锁成功之后,setHead(node),将B节点传进来了,并将头节点指向B,将B的线程置为null,B的前驱也置为null。并将p(哨兵节点的后继节点也置为null)
这里在做的事情是将B节点变成新的哨兵节点,将原来的哨兵节点去掉,等待GC。
p.next = null; // help GC
failed = false;
=========================================================================
以下是B 获得锁之后的图:
=========================================================================
同理B unlock 的时候,C 也会被唤醒,获得锁,并将C设置为哨兵节点,将B 移除。