【并发编程】AQS原理详解笔记1
笔记来源
AQS核心思想
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制
图片来自JavaGuide
AQS 使用volatile int成员变量state表示同步状态。state可以通过 protected 类型的getState()、setState()和compareAndSetState() 进行操作
//返回同步状态的当前值
protected final int getState() {return state;
}// 设置同步状态的值
protected final void setState(int newState) {state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS的作用
AQS 是一个抽象类,定义了资源获取和释放的通用流程,通过封装复杂的线程同步机制为程序员提供一个框架用于实现各种同步器。AQS可以理解为各种同步器的底座
AQS 为什么使用CLH变体队列的?
CLH锁是基于自旋锁的改进,自旋锁通过不断对一个原子变量做CAS操作来尝试获取锁。但是在多线程环境下会存在部分线程饥饿问题
CLH锁引入了一个队列来组织并发竞争的线程,
- 队头是当前占用锁的线程,每个线程会进入队列,通过自旋监控前一个线程的状态;
- 每个线程按顺序排队;
AQS在CLH锁基础上进一步优化,
- 线程获取锁失败,会短暂自旋尝试获取锁,如果还是失败就会进入阻塞状态;
- 双向队列,增加了next指针,可以直接唤醒后继节点;
AQS 将每条请求共享资源的线程封装成一个CLH变体队列的一个结点(Node)来实现锁的分配
AQS性能好的原因
内部使用大量的CAS操作,主要用于队列初始化和线程入队的并发安全(因为队列是共享资源)
AQS 中为什么Node节点需要不同的状态?
用waitStatus表明Node节点的不同含义,控制状态间的流转
- 0:新线程加入队列之后,初始状态为0
- SIGNAL -1:新线程加入后,前一个线程的状态就变成了SIGNAL,表示前继线程释放锁之后,需要对新节点进行唤醒操作。清除SIGNAL状态,表示已经执行了唤醒操作(SIGNAL->0是前继线程的工作留痕)
- CANCELLED 1:如果一个线程在队列中等待获取锁时,因为某种原因失败了,该线程的状态就会变为CANCELLED ,表明取消获取锁,这种状态的节点是异常的,无法被唤醒,也无法唤醒后继线程