AQS是什么?
AQS,全称AbstractQueuedSynchronizer,抽象队列同步器,是用来构建锁或者其他同步组建的基础框架;它使用了一个int成员变量state来表示同步的状态,并内置了一个队列来完成需要获取资源的线程的排队工作;锁是面向锁的使用者的,而AQS则是面向锁的编写者的
AQS的工作原理是什么?
三个组件:state,队列,exclusiveOwnerThread
state:实现锁信息的同步
CLH队列:对获取锁失败的线程进行管理
exclusiveOwnerThread:用于表示当前是哪个线程正在持有锁
AQS依赖底层的同步队列,当一个线程获取锁失败之后,那么就会将其封装为一个node节点然后到队列的末尾,并阻塞这个线程,当持有锁的线程释放之后,会将队头的节点中的线程唤醒,使其再次尝试获取锁
AQS的node节点的状态值有哪些?
cancelled:节点引用的线程由于等待超时或者打断被取消之后,节点会变成cancelled状态
signal:后续节点需要被唤醒时,当前节点就会变成signal状态,
condition:表示当前节点进入了condition队列的状态
propagate:释放共享锁的时候会头节点使用
AQS的阻塞+自旋是在哪个方法里面?
在acquireQueued里面
java
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);
}
}
AQS中acquire的工作流程是怎么样的?
java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
-
使用tryAcquire尝试获取锁,获取锁成功,直接返回,如果失败,进入第2步
-
进入addWaiter,将当前线程封装成一个node,然后放入CLH队列的尾部
-
进入acquireQueued
-
当前节点的前驱节点是CLH队列的头节点,那么通过tryAcquire来尝试获取锁,如果获取成功,那么把当前节点设置为头节点
-
当前节点的前驱节点不是CLH队列的头节点,或者tryAcquire获取锁失败,那么会进入shouldParkAfterFailedAcquire并判断它的前驱节点的waitStatus状态:
- 如果是signal,返回true
- 如果是cancel,那么会找到从这个节点开始往前数,第一个不是cancel状态的节点,并把这个节点当成自己的头节点,删除掉中间的所有cancel节点,返回false
- 如果是其他情况,那么将前驱节点的waitStatus状态修改为signal,返回false
如果shouldParkAfterFailedAcquire返回true,那么阻塞,否则进入下一次循环
-
-
如果阻塞时被中断,那么进入selfInterrupt,打断当前线程
AQS为什么要设计为双向链表?
因为有的操作需要使用到前驱节点
当判断这个线程是否需要阻塞的时候,需要判断前一个前驱节点的waitStatus状态是否为signal
另外如果前驱节点是一个cancelled状态的节点,那么往前找到第一个不是cancelled状态的节点位置
基于AQS实现的ReentrantLock在非公平的情况下的lock和unlock过程是怎么样的?
lock
java
public void lock() {
sync.lock();
}
java
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
java
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;
}
在调用lock之后,会通过cas判断state是否等于0并尝试把它设置为1(尝试加锁),如果cas成功,那么会把锁的独占线程设置为当前线程,这是非公平锁的原因;
如果加锁失败,会进入acquire,然后进入tryAcquire方法,然后会再一次判断state,如果state等于0,那么会再尝试加一次锁,如果state不等于0,那么会判断要当前线程是否是持有锁的线程,如果是,那么会将state加1,这也是可重入的原因;
如果tryAcquire没有获取到锁,那么会将这个线程封装成一个node,会通过cas将结点插入到aqs链表的尾部;
然后在acquireQueued中,通过cas+自旋的方式尝试获取锁,但是只有当前结点时是头结点的时候才能尝试获取锁,这中间如果遇到了中断,那么是不会抛出异常的(使用lockInterruptibly则会抛异常),会继续自旋并阻塞,直到获取到锁为止;
unlock
java
public void unlock() {
sync.release(1);
}
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
java
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;
}
java
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);
}
释放锁相对比较简单,会先判断释放锁的线程是不是持有锁的线程,如果是,那么就会释放锁,由于是可重入锁,所以需要等state变成0之后才能完全释放锁,把持有锁的线程置为null并唤醒后面的线程;
AQS的模板方法模式体现在哪里?
acquire
java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法中,先定义了获取锁的流程是tryAcquire,获取失败之后通过addWaiter把线程包装成一个结点放入aqs队列,然后再通过acquireQueued自旋获取锁;这一套流程已经定义好,具体的实现可以由子类去重写tryAcquire方法来决定
acquireInterruptibly
java
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
acquireShared
java
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
acquireSharedInterruptibly
java
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
release
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
releaseShared
java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
AQS的哪些方法需要被子类重写?
tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively
AQS没有抽象方法,为什么要使用abstract修饰?
AQS设置为抽象类的原因是它必须要实现其中的某一些方法才能使用,比如tryAcquire,tryRelease等核心方法,作者可能并不希望使用者直接初始化AQS并使用,所以使用abstract修饰
AQS中那些未实现的方法为什么不声明为抽象方法?
因为AQS是大框架,很多同步组件都是基于AQS开发,其中有一些组件可能是需要实现其中的两个或者三个方法即可,不需要重写全部方法,如果把那些未实现的方法声明为抽象方法,那么每个方法都要重写,可能对锁的编写者来说不那么方便
Condition的工作原理?
Condition是aqs中的一个内部类,可以通过await和signal实现更加精细的线程同步机制,类似与object的wait和notify
Condition内部有一个双向链表,当线程调用了Condition的await之后,这个node会被放到Condition队列的尾部中,释放锁,并失去获取锁的资格,不断自旋阻塞等待重回CLH队列;
当其他线程调用这个Condition的signal之后,这个node会从Condition队列的头部中移除,并加入到CLH队列的尾部中等待获取锁,会带上锁的重入次数;
Condition的await、signal和Object的wait、notify有什么区别?
Condition的await和signal是配合lock使用的,Object的wait和notify是配合sync使用的
Condition可以响应中断,Object不行
同一个lock的情况下,Condition可以有多条Condition队列,Object只会有一个