AQS
AbstractQueuedSynchronizer
, 和 CAS
一样, 也是Java
并发编程的一个基石.
Java提供的同步器开发框架.
将用户和真正意义上的底层隔离 – 用户实现同步控制算法时不再需要使用 JVM 提供的最底层的 API.
有临界区要保护. 互斥量(Mutex); 信号量(Semaphore)。
Java 线程是用户到内核映射, 内核级线程, 调度的是OS。
互斥: 允许临界区一个线程进入.
信号量: 允许临界区N个线程进入. 也就是并发量等于1的就是互斥量.
Condition: 条件变量, 是操作系统提供的, 基于整数的, 生产者/消费者的模型.
总结
- 基于 Java 实现.
- 提供同步元语和高性能生产/消费者结构.
- 提供
Non-Blocking
能力(CAS
/TryLock
). - 提供定时能力.
- 提供中断能力.
- 提供线程间协作(Condition).
- 提供扩展数据结构的能力.
- 内部提供整数状态.
- 封装CAS操作. acquire/release 都基于cas状态; cas失败触发在等待队列中等待.
- 封装高性能的CLH队列. CAS失败自动进入队列; 条件等待自动进入队列.
CLH队列 是线程安全的, 同时也是无锁编程(Lock-Free)的.
性能层面
- 先竞争进入队列,成本低。
- 再进入临界区是排队进去的。
里面还有公平和非公平的思考.
AQS 工作原理
加锁工作原理。
void acquire() {
// 尝试获取 所以继承的话要重写 tryAcquire() tryRelease() 方法
while(!tryAcquire()){
// 如果队列中没有当前线程
queue.add(currentThread); // 加入队列
park(); // 休眠线程
}
}
控制反转(IOC)的, 获取锁的逻辑由开发者提供。
竞争分为2个阶段. while: 部分进行自旋竞争; 竞争失败的在队列中休眠(park), 直到竞争成功者退出(unpark)
void acquire(){
while(!tryAcquire()){
// 如果队列中没有当前线程: 加入队列
queue.add(currentThread);
pack(); // 休眠线程
}
}
// 用户实现继承类重写这个方法
@Override
boolean tryAcquire(){
// CAS 获得锁的逻辑
return 获得锁是否成功
}
公平性
什么是公平?
FIFO
先入先出.Random
随机.
公平锁: 除了第一个线程, 其余线程都会阻塞, cpu唤醒线程的开销会更大。
非公平锁: 多个线程去获取锁的时候, 会直接去尝试获取.如果能获取到, 直接获得锁, 不用再阻塞
后来者能抢占(插队), 就是不公平的. 所以不公平的插队, 可以让插队线程跳过陷入阻塞的过程, 意味着不公平锁更快, 吞吐量更大。
ReentrantLock
实现了2个.
公不公平看获取锁的逻辑. 不公平, 允许新进来的线程进入竞争。
// ReentrantLock
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* Acquires only if reentrant or queue is empty. 只能在队列是空或者可重入时获取
*/
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 如果有排队的线程, 直接进行休眠
// hasQueuedThreads 如果有线程在等待就返回 true
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
/**
* Acquires only if thread is first waiter or empty. 仅当线程是第一个等待者或者为空时才获取.
*/
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
总结:如果是公平锁, 一旦已经有线程在排队了, 就不再尝试获取锁了; 对于非公平锁而言, 无论是否有线程在排队, 都会尝试获取一下锁, 获取不到再去排队。
CLH 队列实现
基于链表+CAS实现. 尾部插入, 头部删除.
- 避免了双向链表
- 单向链表性能好