- AQS
指AbstractQueuedSynchronizer类。
AQS是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现。AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类。AQS也被称为队列同步器,是用来构建锁或者其他同步组件的基础框架。
使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
主要使用方式就是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态,对同步状态进行更改用到了AQS的getState setState和compareAndSetState方法。 - CLH队列(Craig, Landin, and Hagersten lock queue)
CLH队列是AQS中“等待锁”的线程队列。在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。CLH就是管理这些“等待锁”的线程的队列。
CLH是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。
- Node简介
Node是CLH队列的节点,代表“等待锁的线程队列”。
(01) 每个Node都会一个线程对应。
(02) 每个Node会通过prev和next分别指向上一个节点和下一个节点,这分别代表上一个等待线程和下一个等待线程。
(03) Node通过waitStatus保存线程的等待状态。
(04) Node通过nextWaiter来区分线程是“独占锁”线程还是“共享锁”线程。如果是“独占锁”线程,则nextWaiter的值为EXCLUSIVE;如果是“共享锁”线程,则nextWaiter的值是SHARED。 - 流程图简单描述AQS的原理
一个线程获取同步状态,若能够获取锁便执行;
若获取不到,便将此线程封装成一个node节点,加入CLH队列尾部。若节点的前驱为头节点(头节点为正在运行的节点),尝试获取锁,若成功,便将此节点设置为前驱节点,并执行;否则继续等待。
- 使用AQS实现共享锁的思路
- acquireShared
此方法是共享模式下线程获取共享资源的顶层入口。它会获取指定量的资源,获取成功则直接返回,获取失败则进入等待队列,直到获取到资源为止,整个过程忽略中断
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
这里tryAcquireShared()需要自定义同步器去实现。但是AQS已经把其返回值的语义定义好了:负值代表获取失败;0代表获取成功,但没有剩余资源;正数表示获取成功,还有剩余资源,其他线程还可以去获取。所以这里acquireShared()的流程就是:
(1)tryAcquireShared()尝试获取资源,成功则直接返回;
(2)失败则通过doAcquireShared()进入等待队列,直到获取到资源为止才返回。
- releaseShared
此方法是共享模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果成功释放且允许唤醒等待线程,它会唤醒等待队列里的其他线程来获取资源。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared是需要我们自己实现的方法,如果tryReleaseShared成功的话就执行doReleaseShared唤醒下一个线程。
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);//唤醒后继
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)// head发生变化
break;
}
}
- 使用AQS实现共享锁代码实现
public class AQSSharedLock implements Lock {
private final Sync sync = new Sync(3);
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -8540764104913403569L;
Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("锁资源数不能为负数~");
}
setState(count);
}
@Override
public int tryAcquireShared(int reduceCount) {
for (;;) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
public boolean tryReleaseShared(int returnCount) {
for (;;) {
int current = getState();
int newState = current + returnCount;
if (compareAndSetState(current, newState)) {
return true;
}
}
}
}
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void unlock() {
sync.releaseShared(1);
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
}
我们重写了tryAcquireShared和tryReleaseShared,通过自旋锁来保证这两个操作的原子性,然后重写了锁的lock和unlock方法,调用acquireShared和releaseShared完成锁的枷锁以及级锁操作。