CAS 与 AQS
5.9.16 CAS (Compare and Swap) 原理
- 在并发中判断是否被其他线程修改的方法,保证多线程情况下修改不会出错。假设某个变量应该是某值,如果是,则继续修改;如果不是则该值被其他线程修改过,则不修改。
- 三个操作数:内存值 V, 预期值 A 和要修改的值 B. 仅当 V 与 A 相同时,才将 V 修改为 B,最后返回 V.
- 应用场景:
- 乐观锁
- 并发容器
- 原子类
- 加载 Unsafe 工具直接操作内存数据
Java 通过本地方法编写了 Unsafe 类,提供了硬件级别的原子操作。
通过 valueOffset 这样的内存偏移地址在内存中获取到 value 原值,之后提供 Unsafe 来实现 CAS. - 使用 volatile 修饰 value 字段保证可见性
- 加载 Unsafe 工具直接操作内存数据
- 缺点:
- ABA 问题:线程 1 在比较时发现值符合,但是在准备修改期间被其他线程变更值为其他值又变回原值,CAS 无法知道这些变更。可以利用版本号的方法解决。
- 自旋时间过长
5.9.17 AQS (Abstract Queued Synchronizer)
AQS 是一个用于构建锁、同步器和协作工具的工具类。利用它可以简单构建线程协作类。
核心为三部分:
- state
- 会因为具体实现类的不同而具有不同含义。在
Semaphore中表示剩余信号量个数。在CountDownLatch中表示还要倒数的个数。在ReentrantLock中表示锁的占有情况,比如可重入次数。 - 被
volatile修饰,允许并发修改。修改 state 的方法需要保证线程安全,例如getState(),setSate(),compareAndSetState()等。这些方法都依赖 atomic 包支持。
- 会因为具体实现类的不同而具有不同含义。在
- 控制线程对锁争抢和配合的 FIFO 队列
- 用来存放等待中的线程。
- 双向链表形式。
- 期望协作工具类要实现的获取和释放等重要方法
- 获取方法
依赖 state 变量,经常会阻塞。例如Semaphore中的acquire(),CountDownLatch中的await() - 释放方法
依赖 state 变量,不会阻塞。例如Semaphore中的release(),CountDownLatch中的countDown()
- 获取方法
示例:
利用 AQS 编写一个一次性门闩线程协作器。线程调用 await() 进入阻塞状态;当某一线程调用 signal() 则放行所有线程。
java
public class Latch {
private Sync sync = new Sync();
// 获取
public void await() {
sync.acquireShared(0);
}
// 释放
public void signal() {
sync.releaseShared(0);
}
private class Sync extends AbstractQueuedSynchronizer {
@Override
protected int tryAcquireShared(int arg) {
return (getState() == 1) ? 1 : -1; // 小于 0 的时候阻塞
}
@Override
protected boolean tryReleaseShared(int arg) {
setState(1); // state = 1 时放行
return true; // 为真时放行
}
}
}