AQS
AQS(AbstractQueuedSynchronizer)定义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。Java并发编程核心java.concurrent.util包当中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这个行为的抽象就是基于AQS。例如Semaphore、CountDownLatch、ReentrantLock、BlockingQueue等都是基AQS实现的。
AQS具备特性:
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
AQS中几个关键的概念
state:状态器 | 表示资源的可用状态(同步状态),线程获取锁、释放锁时需要修改,通过CAS完成对state值的修改 state=1 : 表示锁被某个线程占用了 state=0 : 表示锁处于空闲的状态 |
waitStatus | 当前节点在队列中所处的状态,有5种值(默认值0) 0:初始状态,当一个Node被初始化的时候的默认值 CANCELLED:1,表示节点线程获取锁的请求已经取消了 CONDITION:-2,表示节点在等待队列中,节点线程等待唤醒 PROPAGATE:-3,当前节点线程处在SHARED情况下,该字段才会使用 SIGNAL:-1,表示下一个链接的节点线程可以被唤醒 |
AQS定义资源访问的两种方式
|
Exclusive-独占:只有一个线程能访问资源,如ReentrantLock
Share-共享:多个线程可同时执行访问,如Semaphore/CountDownLatch
|
AQS定义的两种队列 | 同步等待队列:CLH,只有在该队列中的节点才能获取锁 条件等待队列: |
同步等待队列CLH
CLH是基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。AQS是通过将每个请求共享资源的线程封装成一个节点来实现锁的分配。
条件等待队列
ReentrantLock
公平锁的加锁过程
Q: 第4步中为什么获取了锁还要中断线程?
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
A:Thread.interrupted()方法在返回true的同时会把中断状态重置为false,为了避免丢失中断状态,就需要在acquire(..)方法里重新调用一下selfInterrupt()把中断状态设置为true。
在 CLH队列中是不响应中断的,那么等他出队列了就应该把补上。线程在等待资源的过程中被唤醒,唤醒后还是会不断地去尝试获取锁,直到抢到锁为止。也就是说,在整个流程中,并不响应中断,只是记录中断记录。最后抢到锁返回了,那么如果被中断过的话,就需要补充一次中断。
解锁过程
ReentrantLock在解锁的时候,并不区分公平锁和非公平锁
public void unlock() {
sync.release(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer
//所以锁的释放来源于AQS底层提供的基础能力
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 final boolean tryRelease(int releases) {
int c = getState() - releases;
// 只有自己才能释放自己的锁,否则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 同步状态state=0,表示持有锁的线程全部被释放,则将当前独占锁的线程设置为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 更新state
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
// 获取头结点的状态
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
Node s = node.next;
//如果下一个节点是null或者下一个节点被cancelled,就找到队列最开始的非cancelled的节点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
// 如果当前节点不为空,而且状态<=0,就被当前节点unpark
if (s != null)
LockSupport.unpark(s.thread);
}