AQS 系列:
首先,从类注释可以得到的信息:
- 提供了一种框架,自定义了先进先出的同步队列,让获取不到锁的线程能进入同步队列中排队;
- 同步器有个状态字段,我们可以通过状态字段来判断能否得到锁,此时设计的关键在于依赖安全的 atomic value 来表示状态(虽然注释是这个意思,但实际上是通过把状态声明为 volatile,在锁里面修改状态值来保证线程安全的);
- 子类可以通过给状态 CAS 赋值来决定能否拿到锁,可以定义那些状态可以获得锁,哪些状态表示获取不到锁(比如定义状态值是 0 可以获得锁,状态值是 1 就获取不到锁);
- 子类可以新建非 public 的内部类,用内部类来继承 AQS,从而实现锁的功能;
- AQS 提供了排它模式和共享模式两种锁模式。排它模式下:只有一个线程可以获得锁,共享模式可以让多个线程获得锁,子类 ReadWriteLock 实现了两种模式;
- 内部类 ConditionObject 可以被用作 Condition,我们通过 new ConditionObject () 即可得到条件队列;
- AQS 实现了锁、排队、锁队列等框架,至于如何获得锁、释放锁的代码并没有实现,比如 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 这些方法,AQS 中默认抛 UnsupportedOperationException 异常,都是需要子类去实现的;
- AQS 继承 AbstractOwnableSynchronizer 是为了方便跟踪获得锁的线程,可以帮助监控和诊断工具识别是哪些线程持有了锁;
- AQS 同步队列和条件队列,获取不到锁的节点在入队时是先进先出,但被唤醒时,可能并不会按照先进先出的顺序执行。
AQS 的注释还有很多很多,以上 9 点是比较重要的注释总结。
AbstractQueuedSynchronizer 继承关系,核心成员变量,主要构造函数:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
// 同步器的状态,根据当前状态进行判断是否可以获得当前锁
// 如果当前state是0,那么可以获得锁
// 可重入锁,每次获得锁+1,每次释放锁-1
private volatile int state;
// 自旋超时阀值,单位纳秒
// 当设置等待时间时才会用到这个属性
static final long spinForTimeoutThreshold = 1000L;
// 同步队列的头
private transient volatile Node head;
// 同步队列的尾
private transient volatile Node tail;
// Node
// 即使同步队列的Node,也是条件队列的Node
static final class Node {...}
// 条件队列,从基础属性上可以看出是链表队列
public class ConditionObject implements Condition, java.io.Serializable
//----------------------------------Node------------------------------------------------------
// 静态内部类,既是同步队列的节点,也是条件队列的节点
// 注:从 next、prev 可以看出同步队列和条件队列的实现都是双向链表(条件队列的head、tail在上面已经定义了)
static final class Node {
// 当前节点的线程
volatile Thread thread;
// 当前节点的前节点
// 节点 acquire 成功后就会变成head
// head 节点不能被 cancelled
volatile Node prev;
// 当前节点的下一个节点
volatile Node next;
/**
* 两个队列共享的属性
* 注:AQS 的 state 决定能不能拿锁,node 的状态影响哪个线程能拿锁
*/
// 表示当前节点的状态,通过节点的状态来控制节点的行为
// 普通同步节点就是 0 ,实际一共5种状态(1,0,-1,-2,-3)
volatile int waitStatus;
// 被取消,在同步队列中出错时会被(暂时)置为cancel,随后就会删除
static final int CANCELLED = 1;
// 表示其后有待唤醒的节点,存在的意义是防止有节点被忘记唤醒
static final int SIGNAL = -1;
// 表示当前 node 正在条件队列中,当有节点从同步队列转移到条件队列时,状态就会被更改成 CONDITION
static final int CONDITION = -2;
// 无条件传播,共享模式下,该状态的进程处于可运行状态
static final int PROPAGATE = -3;
// 在同步队列中,nextWaiter 只是表示当前 Node 是排它模式还是共享模式
// 在条件队列中,nextWaiter 就是表示下一个节点元素
Node nextWaiter;
// node是共享模式
static final Node SHARED = new Node();
// node是排它模式
static final Node EXCLUSIVE = null;
}
//----------------------------------ConditionObject-----------------------------------------
// 条件队列,从属性上可以看出也是双向链表
// 注:关于命名,条件队列中将 node 叫做 waiter
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
// 条件队列中第一个 node
private transient Node firstWaiter;
// 条件队列中最后一个 node
private transient Node lastWaiter;
}
}
我们先来看看 AQS 继承的 AbstractOwnableSynchronizer,AbstractOwnableSynchronizer 的作用就是为了知道当前是那个线程获得了锁,方便监控用
Condition 接口
我们再来看一下条件队列 ConditionObject 实现的 Condition 接口:
public interface Condition {
// 如果线程被打断,抛 InterruptedException 异常
void await() throws InterruptedException;
// 虽然入参可以是任意的时间,但底层仍然转化成纳秒
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 返回的 long 值表示剩余的给定等待时间,如果返回的时间小于等于 0 ,说明等待时间过了
// 选择纳秒是为了避免计算剩余等待时间时的截断误差
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 和 wait 方法比较,不能被打断,其余一样
void awaitUninterruptibly();
// 返回值表示目前为止,指定日期是否到期,true 表示没有过期,false 表示过期了
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待的线程,在被唤醒前必须先获得锁
void signal();
// 唤醒条件队列中的所有线程
void signalAll();
}
AQS 实现 Condition 接口的作用:
- 当 lock 代替 synchronized 来加锁时,Condition 就可以用来代替 Object 中相应的监控方法了,比如 Object.wait、Object.notify、Object.notifyAll 这些方法;
- 提供了一种线程协作方式:一个线程被暂停执行,直到被其它线程唤醒;
- Condition 实例是绑定在锁上的,通过 Lock#newCondition 方法可以产生该实例;
- 除了特殊说明外,任意空值作为方法的入参,都会抛出空指针;
- Condition 提供了明确的语义和行为,这点和 Object 监控方法不同。
AQS 设计模型
AQS 的核心思想是:开源节流,两种模式加锁,
- 开源:所有线程都能进入同步队列
- 节流:只有极少数线程能运行
- 两种加锁模式
- 独占与共享
- 独占:只有1个线程能执行
- 共享:1个线程在执行前会叫醒其他线程一起执行
- 公平与非公平
- 公平:进入同步队列,服从同步器调度,先入先出
- 非公平:可以不进入同步队列,CAS修改state成功直接拿锁运行
- 独占与共享
关于独占、共享、公平、非公平理解一下这张图:
注:独占、共享、公平、非公平都是对于线程获取锁而言的,在锁释放时是没有这些概念的