AbstractQueuedSynchronizer- AQS
AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。有了AQS以后,更多的协作工具类都可以很方便得被写出来。一 句话总结:有了AQS ,构建线程协作类就容易多了。
使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架,利用了 int 类型表示状态,使用方法是继承利用了模板方法
子类通过继承并通过实现它的方法管理其状态 { acquire和 release } 的方法操纵状态
可以同时实现排它锁和共享锁模式(独占、共享)
AQS 具体实现大致思路:AQS 内部维护了一个 CLH 队列管理锁,线程首先尝试获取锁,如果失败就将当前线程以及等待状态等信息包装成一个 Node 节点加入到同步队列,接着不断循环尝试获取锁,条件是当前节点为 Head 直接后继才会尝试,如果失败就会阻塞自己直到自己被唤醒,当持有锁的线程释放锁的时候会唤醒队列中的后继线程,Semaphore、ReentrantLock、CountDownLatch 内部有一个 Sync 类,Sync 类继承了 AQS
源码
AQS最核心的就是三大部分:
◆ state
这里的 state 的具体含义,会根据具体实现类的不同而不同。比如在Semaphore里它表示”剩余的许可证的数量“,而在CountDownLatch里,它表示"还需要倒数的数量”。state 是 volatile 修饰的,会被并发地修改,所以所有修改 state 的方法都需要保证线程安全,比如 getState、setState 以及compareAndSetState 操作来读取和更新这个状态。这些方法都依赖于J.U.C.Atomic 包的支持
/**
* The synchronization state.
*/
private volatile int state;
在 ReentrantLock中,state 用来表示"锁”的占有情况,包括可重入计数,当state的值为0的时候,标识改Lock不被任何线程所占有
◆ 控制线程抢锁和配合的FIFO队列
这个队列用来存放“等待的线程”,AQS就是"排队管理器”当多个线程争用同一把锁时,必须有排队机制将那些没能拿到锁的线程串在一起。当锁释放时,锁管理器就会挑选一个合适的线程来占有这个刚刚释放的锁
AQS 会维护一个等待的线程队列,把线程都放到这个队列里,这是一个双向链表的形式
◆ 期望协作工具类去实现的获取/释放等重要方法
这里的获取和释放方法,是利用AQS的协作工具类里最重要的方法,是由协作类自己去实现的,并且含义各不相同
获取方法:获取操作会依赖state变量,经常会阻塞(比如获取不到锁的时候),在Semaphore中获取就是acquire方法,作用是获取一个许可证,能不能获取到也取决于 state 变量,而在CountDownLatch里面获取就是await方法,作用是"等待“直到倒数结束
释放方法:释放操作不会阻塞,在 Semaphore 中,释放就是 release 方法,作用是释放一个许可证,CountDownLatch 里面,获取就是 countDown 方法作用是"倒数1个数“
用法
自己用 AQS 实现一个简单的 CountDownLatch
第一步:写一个类,想好协作的逻辑,实现获取/释放方法。
第二步:内部写一个Sync类继承 AbstractQueuedSynchronizer
第三步:根据是否独占来重写tryAcquire/tryRelease或tryAcquireShared (int acquires)和tryReleaseShared(intreleases)等方法,在之前写的获取/释放方法中调用AQS的acquire/release或者Shared方法
public class Test {
private final Sync sync = new Sync();
// 0 关闭 1 打开
// 获取 共享所模式 利用 AQS
public void await() { // 谁调用谁进入等待状态
// if (tryAcquireShared(arg) < 0) 代表要排队阻塞
sync.acquireShared(0); // 后去所调用 acquireShared 这里面的方法
}
public void signal() { // 之前阻塞的线程都会唤醒
sync.releaseShared(0);
}
private class Sync extends AbstractQueuedSynchronizer {
// tryAcquireShared 是 acquireShared 里面的,我们要自己实现
@Override
protected int tryAcquireShared(int arg) {
// getState 这个值由 signal 来改变
return (getState() == 1) ? 1 : -1;
}
// 返回 true 把之前所有线程唤醒
@Override
protected boolean tryReleaseShared(int arg) {
setState(1);
return true;
}
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "尝试获取latch,获取失败等待");
test.await();
System.out.println("开闸放行" + Thread.currentThread().getName() + "继续运行");
}).start();
}
Thread.sleep(5000);
test.signal();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "尝试获取latch,获取失败等待");
test.await();
System.out.println("开闸放行" + Thread.currentThread().getName() + "继续运行");
}).start();
}
}
运行结果:
Thread-0尝试获取latch,获取失败等待
Thread-2尝试获取latch,获取失败等待
Thread-1尝试获取latch,获取失败等待
Thread-3尝试获取latch,获取失败等待
Thread-4尝试获取latch,获取失败等待
Thread-5尝试获取latch,获取失败等待
Thread-6尝试获取latch,获取失败等待
Thread-7尝试获取latch,获取失败等待
Thread-8尝试获取latch,获取失败等待
Thread-9尝试获取latch,获取失败等待
开闸放行Thread-0继续运行
开闸放行Thread-2继续运行
开闸放行Thread-1继续运行
开闸放行Thread-3继续运行
开闸放行Thread-4继续运行
开闸放行Thread-7继续运行
开闸放行Thread-5继续运行
开闸放行Thread-6继续运行
开闸放行Thread-9继续运行
开闸放行Thread-8继续运行
Thread-10尝试获取latch,获取失败等待
开闸放行Thread-10继续运行
Process finished with exit code 0
最后
回顾一下 AQS 的核心内容
- state
- 控制线程抢锁和配合的FIFO队列
- 期望协作工具类去实现的获取/释放等重要方法