AQS (AbstractQueuedSynchronizer)抽象的队列式同步器
其中Sync及其子类NonfairSync、FairSync和node均为ReentrantLock的静态内部类
一、AQS内部结构 state: 同步器状态,被volatile修饰,通过cas争抢状态
exclusiveOwnerThread: 互斥锁持有的线程(AbstractOwnableSynchronizer的属性,aqs的父类)
head: 同步等待队列的头部,被volatile修饰
tail: 同步等待队列的尾部,被volatile修饰
node: AQS的静态内部类,CLH的基础节点
二、Node内部结构 prev: 指向前一个node节点,被volatile修饰
next: 指向后一个node节点,被volatile修饰
waitStatus: 信号状态,默认是0,被volatile修饰
CANCELLED = 1: 取消状态,当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后,节点将不会再变化
SIGNAL = -1: 等待触发状态,后继节点在等待当前节点唤醒。后继节点入队后,会将前继节点的状态更新为SGNAL
CONDITION = -2: 节点等待在confition上,当其他线程调用confition的signal()方法后,CONDITION状态的节点将从等待队列转移到同步队列中
PROPAGATE = -3: 共享模式下,前继节点不仅会唤醒后继节点,同时也可能会唤醒后继节点的后继节点
thread:当前节点关联的客户线程,被volatile修饰 三、执行流程 流程图
lock()流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 Sync的lock() 1 NonfairSync.lock() 非公平锁 2 调用父类aqs的compareAndSetState(第一次)cas争抢同步器状态, 1.1 成功,调用父类aos的setExclusiveOwnerThread(Thread.currentThread()),修改exclusiveOwnerThread为当前线程 1.2 失败,调用aqs的acquire()尝试争抢和排队 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 2.1 调用aqs的tryAcquire,进入子类reentrantLock的tryAcquire(arg) 2.1.1 调用NonfairSync的tryAcquire里面的nonfairTryAcquire(acquires),尝试获取同步器状态 2.1.1.1 同步器状态为0,(第二次)cas争抢同步器状态 2.1.1.1.1 成功,设置AOS的exclusiveOwnerThread为当前线程,返回true,不进行排队 2.1.1.1.2 失败,返回false,进入排队 2.1.1.2 同步器状态不为0,AOS的exclusiveOwnerThread等于当前线程(一般为重入) 2.1.1.2.1 符合,同步器状态加1,返回true,不进行排队 2.1.1.2.2 不符合,返回false,进行排队 2.2 调用父类aqs的addWaiter(Node mode),为当前线程创建排他的node节点 2.2.1 (旧)尾结点是否为空 2.2.1.1 (旧)尾结点不为空,当前线程节点的上一节点等于(旧)尾结点 2.2.1.1.1 cas设置当前线程节点为(新)尾结点 2.2.1.1.1.1 成功,(旧)尾结点的下一节点等于当前线程节点,返回当前线程节点,等待加入队列 2.2.1.1.1.2 失败,往下执行,进入aqs的enq(node) 2.2.1.2 (旧)尾结点为空,往下执行,进入aqs的enq(node) 2.2.2 进入父类aqs的enq(node),死循环 2.2.2.1 (旧)尾结点为空(说明该队列还没有初始化) 2.2.2.1.1 新创建node节点,cas设置为头结点 2.2.2.1.1 设置成功,尾结点等于头结点,相互指向,形成哨兵节点 2.2.2.1.2 设置失败,重新循环,进入2.2.2 2.2.2.1.2 (旧)尾结点不为空 2.2.2.1.2.1 当前线程节点的上一节点等于(旧)尾结点 2.2.2.1.2.1 cas设置当前线程节点为(新)尾结点 2.2.2.1.2.1.1 成功,(旧)尾结点的下一节点等于当前线程节点,返回当前线程节点的上一节点(旧的尾结点),这里的返回只是为了退出2.2.2,等待加入队列 2.2.2.1.2.1.2 失败,重新循环,进入2.2.2 2.2.3 返回当前线程节点,等待加入队列 2.3 调用父类aqs的acquireQueued(),死循环 2.3.1 获取当前节点的前置节点 2.3.2 前置节点为头节点 && 当前节点再次尝试获取同步器状态,tryAcquire(arg)参见2.1 2.3.2.1 成功, 2.3.2.1.1 调用父类aqs的setHead(node)当前节点设置为头节点,当前节点关联的线程赋值null,上一节点赋值null 2.3.2.1.2 前置节点的下一节点赋值null,辅助gc回收; 不执行finally {if (failed){cancelAcquire(node);}},返回false(也就是不中断),退出2.3 2.3.3 调用父类aqs的shouldParkAfterFailedAcquire(p, node)和parkAndCheckInterrupt() 2.3.3.1 shouldParkAfterFailedAcquire(p, node) 获取失败后park之前重置等待状态的方法 2.3.3.1.1 获取前置节点(当前节点的上一节点)的等待状态 2.3.3.1.2 等待状态等于-1时,返回true,前置节点还没有被触发,当前节点可以被park 2.3.3.1.3等待状态大于0时,前置节点处于取消状态,获取前置节点的上一节点作为当前节点的上一节点,再次判断状态,重复循环,等待状态不大于0是,此时的前置节点的下一节点为当前线程节点,返回false,继续2.3 2.3.3.1.4 等待状态不为以上两种时,cas将状态置为signal也就是-1,返回false,继续2.3 2.3.3.2 前者返回true,进入parkAndCheckInterrupt() 2.3.3.2.1 LockSupport.park(this);阻塞当前线程,等待唤醒,正式入队 2.3.3.2.2 return Thread.interrupted(); 检查被唤醒的线程有没有被中断 2.3.3.2.2.1 返回false,循环2.3->进入2.3.2尝试获取锁或进入2.3.3入队继续等待 2.3.3.2.2.2 返回true,循环2.3->进入2.3.2尝试获取锁或进入2.3.3入队继续等待 2.4 最终:当2.3.3.2.2之后的流程进入2.3.2尝试获取锁成功时,返回值决定是否调用selfInterrupt();
四、CLH双向队列
五、总结 AQS主要实现了一个状态和两个队列(等待队列、条件队列)
5.1、状态变量state 1 2 3 4 private volatile int state;
5.1.1、互斥锁 当AQS只实现互斥锁的时候,每次只要原子更新state的值从0为1成功,即获取到锁,可重入是通过不断把state原子更新加1实现的。
5.1.2、互斥锁和共享锁 当AQS需要同时实现互斥锁+共享锁的时候,低16位存储互斥锁状态,高16位存储共享锁的状态,主要用于实现读写锁。 互斥锁是一种独占锁,每次只允许一个线程独占,且当一个线程独占时,其他线程将无法再获取互斥锁及共享锁,但是它自己可以获取共享锁。 共享锁同时允许多个线程占有,只要有一个线程占有共享锁,所有线程(包括自己)都将无法再获取互斥锁,但是可以获取共享锁。
5.2、等待队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private transient volatile Node head;private transient volatile Node tail;
AQS中维护了一个等待队列,获取锁失败(非tryLock())的线程都将进入这个队列中排队,等待锁释放后唤醒下一个排队的线程(互斥锁模式)。
5.3、条件队列 1 2 3 4 private transient Node firstWaiter;private transient Node lastWaiter;
AQS中还有另外一个非常重要的内部类ConditionObject,它实现了Condition接口,主要用于实现条件锁。 ConditionObject中也维护了一个队列,这个队列主要用于等待条件的成立,当条件成立时,其他线程将signal这个队列的元素,将其移动到AQS的队列中,等待占有锁的线程释放锁后被唤醒 Condition典型的运用场景是在BlockingQueue中实现,当队列为空时,获取元素的线程阻塞在notEmpty条件上,一旦队列中添加了一个元素,将通知notEmpty条件,将其队列中的元素移动到AQS队列中等待被唤醒。