AQS框架应用(五)
定义:
基于AQS框架,Doug Lea给出了很多内置的实现类
实现类:
可以看出,有多个工具类是基于AQS框架实现的
-
独占式同步信号量
-
ReentrantLock
-
Semaphore
-
-
共享式同步信号量
- CountDownLatch
-
混合同步信号量
- ReentrantReadWriteLock
ReentrantLock
定义:
它是基于AQS实现的一个==独占式锁==,分为公平和非公平版本
AQS要求实现子类必须实现tryAcquire()/tryRelease()
方法,接下来介绍ReentrantLock对于这两个方法的实现
组成:
- 公平锁 —— FairSync
- 非公平锁 —— NonFairSync
获取锁
NonFairSync.tryAcquire(int)
定义:
在非公平情况下,去实现tryAcquire()
方法,调用本方法去尝试获取锁
源码:
protected final boolean tryAcquire(int acquires) {
// 调用nonfairTryAcquire()获取锁
return nonfairTryAcquire(acquires);
}
nonfairTryAcquire(int)
定义:
调用本方法去尝试获取锁,套娃
源码:
/**
* 尝试获取非公平锁
* acquires == 1 默认
*/
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 拿到当前同步信号量的值
int c = getState();
// 判断state状态是否为0,为0直接加锁
if (c == 0) {
// 不需要判断同步队列(CLH)中是否有排队等待线程,因为是非公平锁
// 将state修改为1,标记获得锁
if (compareAndSetState(0, acquires)) {
// 独占状态锁持有者指向当前线程
setExclusiveOwnerThread(current);
return true;
}
}
/**
* state状态不为0,判断锁持有者是否是当前线程,
* 如果是当前线程持有 则state+1
*/
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;
}
总结:
- 获取
state
信号量,如果为0,不需要判断CLH是否有等待线程,直接CAS抢锁,return
抢锁是否成功 - 如果不为0,判断是不是自己拿到了锁
- 如果是自己拿到锁,state累加,
return true
- 如果不是自己拿到锁,则
return false
FairSync.tryAcquire(int)
定义:
在公平情况下,去实现tryAcquire()
方法,调用本方法去尝试获取锁
源码:
/**
* 重写aqs中的方法逻辑
* 尝试加锁,被AQS的acquire()方法调用
*/
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 拿到当前同步信号量的值
int c = getState();
// 判断state状态是否为0,为0直接加锁
if (c == 0) {
/**
* ==与非公平锁中的区别==
* 需要先判断 CLH队列 当中是否有等待的节点
* 如果没有则可以尝试CAS获取锁
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 独占线程指向当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果status不为0,则判断持有锁的线程 是否 是当前线程
else if (current == getExclusiveOwnerThread()) {
// 是当前线程,信号量++
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 加锁失败
return false;
}
总结:
- 拿到
state
信号量,如果为0- 判断CLH队列是否有等待线程,有等待线程则
return false
- 如果CLH中没有等待线程,进行CAS抢锁,
return
抢锁是否成功
- 判断CLH队列是否有等待线程,有等待线程则
- 如果不为0
- 判断当前持有锁线程是否为自己,不是则
return false
- 是自己,则让信号量
state = state + acquires
,return true
- 判断当前持有锁线程是否为自己,不是则
释放锁
定义:
公平锁与非公平锁释放锁的逻辑是一样的,都是调用同一个方法tryRelease()
tryRelease()
定义:
该方法用于释放锁
源码:
/**
* 释放锁
*/
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;
}
总结:
- 计算释放后的信号量
- 合法性判断 【是否为拥有锁的线程来释放信号量】
- 当
state==0
表示完全归还信号量 【也就是释放锁】,return true
- 否则修改state,
return false
【释放部分信号量成功,但未释放锁】
应用 BlockingQueue
定义:
BlockingQueue,是用于解决 并发生产者 - 消费者问题 的最有用的类。它保证任意时刻只有一个线程可以进行take或者put操作,并且提供了超时 return null 的机制。
类型:
- 无限队列 —— 几乎可以无限增长
- 有限队列 —— 定义了最大容量
队列数据结构:
- 通常用链表或者数组实现
- 具有FIFO先进先出特性,也有双向队列(Deque)优先级队列
- 主要操作 —— 入队(EnQueue) 与 出队 (DeQueue)
常见的4种阻塞队列
- ArrayBlockingQueue —— 由数组支持的有界队列
- LinkedBlockingQueue —— 由链接节点支持的可选有界队列
- PriorityBlockingQueue —— 由优先级堆支持的无界优先级队列
- DelayQueue —— 由优先级堆支持的、基于时间的调度队列
原理
阻塞队列每一种操作都根据ReentrantLock来加锁进行保证 任意时刻==只有一个线程可以进行take或者put操作==
以ArrayBlockingQueue来举例:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 构造函数中,单例生成 */
final ReentrantLock lock;
// 构造函数,单例生成一个
public ArrayBlockingQueue(int capacity, boolean fair) {
// ...
lock = new ReentrantLock(fair);
// ...
}
// 添加元素操作
public boolean offer(E e) {
checkNotNull(e);
// 获取单例锁
final ReentrantLock lock = this.lock;
// 通过lock锁,锁住
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
// 删除元素操作
public E poll() {
// 获取单例锁
final ReentrantLock lock = this.lock;
// 通过lock锁,锁住
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
}
ArrayBlockingQueue
定义:
队列基于数组实现,容量大小在创建时已经定义
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>();
应用场景:
在线程池中有比较多的应用,生产者 - 消费者场景
工作原理:
基于ReentrantLock保证线程安全,根据Condition实现队列满时的阻塞
LinkedBlockingQueue
定义:
基于链表实现的无界队列(理论上有界)
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
特点:
向无限队列添加元素的所有操作都将永远不会阻塞,[注意这里 不是说不会加锁 保证线程安全],因此它可以增长到非常大的容量。
应用场景:
生产者 - 消费者场景使用时 消费者应该能够像生产者向队列添加消息一样快地消费消息 【否则产生OutOfMemory
异常】
DelayQueue
定义:
由优先级堆支持的、基于时间的调度队列,
- 内部基于无界队列PriorityQueue实现
- 无界队列基于数组的扩容实现
BlockingQueue<String> blockingQueue = new DelayQueue();
要求:
入队的对象必须要实现Delayed接口,而Delayed集成自Comparable接口
应用场景:
电影票
工作原理:
队列内部会根据时间优先级进行排序。延迟类线程池周期执行。
BlockingQueue API
BlockingQueue 接口的所有方法可以分为两大类:
- 向队列添加元素的方法
- 检索这些元素的方法
在队列满/空的情况下,来自这两个组的每个方法的行为都不同。
添加元素
方法 | 说明 |
---|---|
add() | 如果插入成功则返回 true,否则抛出 IllegalStateException 异常 |
put() | 将指定的元素插入队列,如果队列满了,那么会阻塞直到有空间插入 |
offer() | 如果插入成功则返回 true,否则返回 false |
offer(E e, long timeout, TimeUnit unit) | 尝试将元素插入队列,如果队列已满,那么会阻塞直到有空间插入 |
检索元素
方法 | 说明 |
---|---|
take() | 获取队列的头部元素并将其删除,如果队列为空,则阻塞并等待元素变为可用 |
poll(long timeout, TimeUnit unit) | 检索并删除队列的头部,如有必要,等待指定的等待时间以使元素可用,如果超时,则返回 null |
在构建生产者 - 消费者程序时,这些方法是 BlockingQueue 接口中最重要的构建块。
Semaphore
定义:
它是基于AQS实现的一个==共享式==锁,分为公平和非公平版本
AQS要求实现子类必须实现tryAcquire()/tryRelease()
方法,接下来介绍ReentrantLock对于这两个方法的实现
获取资源
非公平的acquire()
定义:
非公平情况下,获取共享式资源
源码:
// AQS父类顶层入口
public final void acquireShared(int arg) {
// 获取锁失败,则节点阻塞
if (tryAcquireShared(arg) < 0)
// AQS维护的节点阻塞方法
doAcquireShared(arg);
}
// Semaphore类实现的tryAcquire()允许外部调用的方法 【非公平】
public boolean tryAcquire() {
// 根据下面nonfairTryAcquireShared()
// 返回正值,说明抢到了锁
// 返回负值,说明没有抢到锁 【资源不足】
return sync.nonfairTryAcquireShared(1) >= 0;
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
/* 获取非公平共享信号量 锁 */
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 拿到当前的信号量 【剩余资源】
int available = getState();
// 尝试分配后的信号量
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
// remaining<0 进入这个return,说明资源不足,返回负值
// CAS 进入这个return,说明分配成功,返回正值
return remaining;
}
}
公平的acquire()
定义:
公平情况下,获取共享式锁
源码:
// AQS父类顶层入口
public final void acquireShared(int arg) {
// 获取锁失败,则节点阻塞
if (tryAcquireShared(arg) < 0)
// AQS维护的节点阻塞方法
doAcquireShared(arg);
}
// Semaphore子类根据AQS要求实现的tryAcquire()
protected int tryAcquireShared(int acquires) {
for (;;) {
// 判断CLH等待队列是否为空 【与非公平锁的不同】
if (hasQueuedPredecessors())
return -1;
// 同样的逻辑
int available = getState();
// 剩余量
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
// remaining<0 进入这个return,说明资源不足,返回负值
// CAS 进入这个return,说明分配成功,返回正值
return remaining;
}
}
总结
- 拿到当前信号量,计算剩余量
remaining
- 如果剩余量小于零,
返回负值
- 如果剩余量大于零
- 非公平锁进行CAS抢锁
- 公平锁判断队列有无节点
- 有则入队
返回负值
- 没有则进行CAS抢锁
- 有则入队
注意:
-
默认情况下,是非公平锁。
-
直接调用 tryAcquire(),走非公平锁的逻辑
-
调用公平还是非公平,取决于实例化的Sync
释放资源
定义:
公平与非公平一样,释放共享式资源
release()
归还资源
源码:
public void release() {
sync.releaseShared(1);
}
// 实现AQS框架指定的tryReleaseShared()方法
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获取当前信号量
int current = getState();
// 做求和得到 新的信号量
int next = current + releases;
if (next < current) // 如果溢出
throw new Error("Maximum permit count exceeded");
// CAS不断设置
if (compareAndSetState(current, next))
return true;
}
}
总结
Semaphore是实现了AQS的一个共享式同步信号量的类,它可以用作服务限流设置
- accquire()方法对共享同步信号量作减法 【取出操作】
- release()方法对共享同步信号量作加法 【归还操作】
CountDownLatch
定义:
它是基于AQS实现的、可中断的一种多线程计数器
开始计数 countDown()
类的入口方法为 countDown()
方法
public void countDown() {
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
// 获取信号量,资源
int c = getState();
// 如果资源为0,则没有资源可给了,返回false
if (c == 0)
return false;
// 剩余资源
int nextc = c-1;
// CAS释放
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
等待总数 await()
类的入口方法为 await()
方法
// 方法入口
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// AQS中,如果中断则终止,尝试获取同步信号量
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
// 小于0表示失败,失败则进入等待队列
doAcquireSharedInterruptibly(arg);
}
// CountDownLatch根据AQS规定的实现tryAcquireShared()
protected int tryAcquireShared(int acquires) {
// 判断当前信号量是否为0
return (getState() == 0) ? 1 : -1;
}
总结
CountDownLatch是一个对多线程计数的类,实现了AQS中共享式同步信号量
- countDown():对信号量–,可用于统计线程数
- await():阻塞,对信号量进行判断,如果信号量
state == 0
,说明不需要再等待任何线程,可以往下执行。