1、什么是AQS?
AQS的全称为(AbstractQueuedSynchronizer),即队列同步器,它是JUC包下面的核心组件,它的主要使用方式是继承,子类通过继承AQS,并实现它的抽象方法来管理同步状态,它分为独占锁和共享锁。很多同步组件都是基于它实现的,比如ReentrantLock就是基于AQS的独占锁实现,它表示每次只能有一个线程持有锁。再比如CountDownLatch、Semaphore等是基于AQS的共享锁实现的,它允许多个线程同时获取锁,并发的访问资源。AQS是建立在CAS上的一种FIFO的双向队列,通过维护一个使用volatile修饰的int类型的state表示资源的锁状态。
2、AQS实现的核心思想?
AQS的核心思想是:如果被请求的共享资源共享,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒是锁分配的机制,这个机制是AQS内部维护着一个FIFO的队列,即CLH队列。CLH队列是FIFO双端双向队列,实现公平锁。线程通过AQS获取锁失败,机会将线程封装成一个Node节点,插入队列尾;当有线程释放锁时,将队列中最先入队的节点拿出来去抢夺锁,然后这个节点出队。
Node节点的组成
static final class Node {
/** 表示节点正处于共享模式下等待标记 */
static final Node SHARED = new Node();
/** 表示节点处于独占锁模式的等待标记 */
static final Node EXCLUSIVE = null;
/** waitStatus值,表示线程取消 */
static final int CANCELLED = 1;
/** waitStatus值,表示线程需要挂起 */
static final int SIGNAL = -1;
/** waitStatus值,表示线程处于等待条件*/
static final int CONDITION = -2;
/**waitStatus值,表示下一个共享模式应该无条件传播*/
static final int PROPAGATE = -3;
/**状态字段*/
volatile int waitStatus;
/**前驱节点 */
volatile Node prev;
/**后继节点 */
volatile Node next;
/**当前线程*/
volatile Thread thread;
/**将此节点入列的线程,用来指向下一个节点*/
Node nextWaiter;
}
AbstractOwnableSynchronizer中的重要属性
AQS抽象类还继承了AbstractOwnableSynchronizer,这个抽象类也有几个关键属性。
//表示当前持有锁的线程
private transient Thread exclusiveOwnerThread;
AQS抽象类中的重要属性
//head节点存储的是当前持有锁的线程,它的后继就是队列的头节点
private transient volatile Node head;
//指向队列的尾节点
private transient volatile Node tail;
//锁的状态
private volatile int state;
//获取当前锁的状态
protected final int getState() {
return state;
}
//修改锁的状态
protected final void setState(int newState) {
state = newState;
}
//通过CAS方式设置state的值,并发环境下使用
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS重要方法
AQS中使用了模板方法模式,自定义同步器需要重写下面的几个AQS提供的模板方法。
isHeldExclusively()//该线程是否处于独占资源。只有用到condition才需要实现它.
tryAcquire(int)//独占方式获取资源,成功返回true,失败返回false
tryRelease(int)//独占方式释放资源,成功返回true,失败返回false
tryAcquireShared(int)//共享方式获取资源。负数表示失败,0表示成功但是没有剩余可用资源;正数表示成功且有剩余资源
tryReleaseShared(int)//共享方式释放资源.成功返回true,失败返回false.
3、迷你版公平锁ReentrantLock的实现
package lock;
import MiniLock.Lock;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;
public class MiniReentrantLock implements Lock {
/*
* 加锁锁的就是资源 用state来表示
* 0表示未加锁 >0表示加锁
* */
private volatile int state;//使用volatile保证可见性
/*
*独占模式:同一时刻只有一个线程可以持有锁,其他线程在未获取到锁时会阻塞
* */
//表示当前独占锁的线程
private Thread exclusiveOwnerThread;
/*
* 需要有两个引用去维护阻塞队列
* 1、Head 指向队列的头节点
* 2、Tail 指向队列的尾节点
* */
//比较特殊:head节点对应的线程就是当前占用锁的线程
private Node head;
private Node tail;
/*
* 阻塞的线程被封装为Node节点,然后放入到FIFO队列
* */
static final class Node{
//前置节点引用
Node prev;
//后置节点引用
Node next;
//封装的线程
Thread thread;
public Node(Thread thread) {
this.thread = thread;
}
public Node() {
}
}
/*
* 加锁
* 模拟的是公平锁
*
* lock的过程是什么样的?
* 情景1:线程进来后发现,当前state=0,这个时候就直接抢锁
* 情景2:线程进来后发现,当前state>0,这个是时候就需要将当前线程入队
* */
@Override
public void lock() {
//第一次获取到锁时,将state设置为1
//第n次重入时,将state设置为n
acquire(1);
}
/*
* 竞争资源
* 1、尝试获取锁,获取成功并占用
* 2、抢占锁失败,阻塞当前线程
* */
private void acquire(int arg){
if(!tryAcquire(arg)){
//尝试获取锁失败:1、当前线程入队 2、当期线程一直自旋尝试获取锁,直到成功获取到锁
//1、入队
Node node = addWaiter();
//2、当前当前线程尝试抢占锁
acquireQueud(node,arg);
}
}
/*
* 第一次尝试获取锁失败后,入队,然后需要一直自旋尝试获取锁,直到获取成功
* */
private void acquireQueud(Node node,int arg){
//只要获取锁成功,才会退出自旋
for (;;){
//什么情况下,当前node被唤醒之后可以尝试去获取呢?
//只有一种情况,当前node是head的后继节点,次啊能被唤醒之后去获取
//先来后到的一种策略
Node pred=node.prev;
//pred==head具有抢占权限
if(pred==head&&tryAcquire(arg)){
//抢占成功,说明当前线程竞争锁成功
//1.设置队列的head为当前线程的node
setHead(node);
//2、当前线程出队,help gc
pred.next=null;
return;
}
System.out.println("线程:"+Thread.currentThread().getName()+",挂起");
//将当前线程挂起
LockSupport.park();
System.out.println("线程:"+Thread.currentThread().getName()+",唤醒");
//当锁被释放时候在,就会唤醒线程
}
}
/*
* 抢占锁失败之后的逻辑是什么?
* 1、需要将当前线程封装为node,加入到阻塞队列
* 2、需要将当前线程park掉,使线程处于挂起状态。
*
* 唤醒线程后该怎么操作?
* 1、检查当前node节点是否为head.next节点(head.next节点是拥有抢占权限的线程,其他的node都没有抢占的权限)
* 2、进行抢占锁
* 成功:1、将当前node设置为head,将老的head出队
* 失败:2、继续park,等待被唤醒
* */
/*
* 当前线程入队,返回当前线程成对应的Node节点
* */
//addWaiter()执行完成之后,保证当前线程入队成功
public Node addWaiter(){
Node newNode = new Node(Thread.currentThread());
/*入队步骤
* 1、找到newNode的前置节点pred
* 2、newNode.prev=pred;
* 3、CAS更新tail为newNode
* 4、pred.next=newNode;
* */
//前置条件,队列已经有等待者node了,当前node不是第一个入队的node
Node pred=tail;
if(pred!=null){
newNode.prev=pred;
//条件成立 说明当前线程成功入队
if(compareAndSetTail(pred,newNode)){
pred.next=newNode;
return newNode;
}
}
//执行到这里的情况
//1、tail==null 队列为空
//2、CAS设置newHead为tail时失败,被其他线程抢先了
enq(newNode);
return newNode;
}
/*
* 自旋入队 只有成功后才返回
* 队列为空或者入队失败 需要一直自旋入队直到成功
* */
private void enq(Node node){
for(;;){
//第一种情况:队列为空 说明当前线程是第一个抢占锁失败的线程
if(tail==null){
//条件成立,说明当前线程给当前持有锁的线程补充head操作成功
if(compareAndSetHead(new Node())){
tail=head;
}
}else{
//说明当前队列中已经有元素了,这里是一个追加node的过程
Node pred=tail;
if(pred!=null){
node.prev=pred;
if(compareAndSetTail(pred,node)){
pred.next=node;
//入队成功 一定要return
return;
}
}
}
}
}
/*
* 尝试获取锁
* true:获取成功
* false:抢占失败
* */
private boolean tryAcquire(int arg){
if(state==0){
/*state=0时就可以直接抢占锁吗?不可以因为是公平锁,要遵循先来后到*/
//当前线程前面没有等待的线程且更改state值成功
if(!hasQueuedProcessor()&&compareAndSetState(0,arg)){
//抢锁成功
this.exclusiveOwnerThread=Thread.currentThread();
return true;
}
//当前锁被占用的时候就会执行这个条件
//条件成立:说明当前线程就是持锁线程
}else if (Thread.currentThread()==this.exclusiveOwnerThread){
//这里面不存在并发,只有当前加锁的线程,才能对state修改
//锁重入的过程
int c=getState();
c=c+arg;
this.state=c;
return true;
}
//返回false的条件
//1、CAS失败
//2、当前线程不是占有锁的线程
return false;
}
//释放锁
@Override
public void unlock() {
release(1);
}
private void release(int arg){
//条件成立:说明线程已经完全释放锁了
//这个时候就需要唤醒阻塞队列中等待的线程
if(tryRelease(arg)){
Node head=this.head;
//先要判断有没有等待者,
if(head.next!=null){
//说明有等待者,公平锁唤醒head.next节点
unparkSuccessor(head);
}
}
}
private void unparkSuccessor(Node node){
Node s=node.next;
if(s!=null&&s.thread!=null){
LockSupport.unpark(s.thread);
}
}
/*
* 完全释放锁成功,则返回true
* 否则说明state>0,返回false
* */
private boolean tryRelease(int arg) {
int c = getState() - arg;
//如果
if (getExclusiveOwnerThread() != Thread.currentThread()) {
throw new RuntimeException("fuck you! must getLock!");
}
//不存在并发问题,同一时刻能进入到这里的线程只有一个就是exclusiveOwnerThread
//条件成立:说明当前线程持有的锁已经完全释放了
if(c==0){
//1.把exclusiveOwnerThread置为null
this.exclusiveOwnerThread=null;
//2、state=0
this.state=c;
return true;
}
this.state=c;
return false;
}
/*
*判断阻塞队列中当前线程的前面是否有等待的线程
* true:表示有
* false:表示没有,当前线程可以尝试抢占
*
* 返回false的情况?
* 1、当前队列为空
* 2、当前线程为head.next节点
* */
private boolean hasQueuedProcessor(){
Node h=head;
Node t=tail;
Node s;
//条件1:h!=t
//成立:说明队列已经有node了
//不成立:1.h=t=null 2、h=t=head,
//条件2:(s=h.next)==null||s.thread!=Thread.currentThread()
//条件2.1:已经有了head.next节点,其他线程再来时需要返回true
//条件2.2:成立:说明当前线程不是h.next节点对应的线程
//不成立:说明当前线程,就是h.next节点对应的线程,需要返回false,线程可以去竞争锁
return h!=t&&((s=h.next)==null||s.thread!=Thread.currentThread());
}
private void setHead(Node node){
this.head=node;
//因为当前node已经是获取成功的线程了,所以队里它的前置设为null,
node.thread=null;
node.prev=null;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
public Node getHead() {
return head;
}
public Node getTail() {
return tail;
}
private static final Unsafe unsafe;
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
stateOffset = unsafe.objectFieldOffset
(MiniReentrantLock.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(MiniReentrantLock.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(MiniReentrantLock.class.getDeclaredField("tail"));
} catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
4、以三个线程来模拟ReentrantLock的公平锁的抢占锁的过程
1、线程1调用reentrantLock.lock()会发生什么?
线程1调用lock方法后,发现state=0,并且发现队列为空,则更新state的值,并将线程1设置为持有锁的线程。
2、线程2进入调用reentrantLock.lock()会发生什么?
- 第一步:
发现state不为0并且线程2不是加锁的线程,说明当前锁已被被的线程持有,然后判断队列是不是为空,如果为空:将head节点设置为当前持有锁的线程。
- 第二步
然后线程2入队,让tail指向线程2的节点,线程2的节点的前驱设置为head,head节点的后继设置为线程2的节点。并且线程2的节点会一直自旋尝试去抢占锁。
3、线程3进入调用reentrantLock.lock()会发生什么?
线程3发现锁已被其他线程抢占,并且当前持有锁的线程不是线程3,队列也不为空,则线程3的节点入队,让线程2节点的后继指向线程3的节点,线程3的节点的前驱指向线程2的节点,tail指向线程3的节点。并且发现head的后继不是线程3的节点,则线程3进入休眠状态。注意:此时线程2的节点一直在尝试竞争锁。
当线程2抢占锁成功后,出队,head存储线程2节点,head的后继指向线程3的节点,这时才唤醒线程3,线程3自旋进行尝试抢占锁。