AQS(AbstractQueuedSynchronizer)
一个同步工具类,使用了模版方法设计模式,内部维护了一个
volatile修饰的state,维护了一个双向链表队列,是一个个Node连接起来的,每个Node内部存储了一个线程,使用AQS过程中大量通过CAS操作去修改state与对队列进行操作。
核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。AQS使用一个voliate int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
Node-waitStatus
这里我们说下Node。Node结点是对每一个等待获取资源的线程的封装,其包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。变量waitStatus则表示当前Node结点的等待状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。
- CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
- SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
- CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
- PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
- 0:新结点入队时的默认状态。
负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。
ReentrantLock
通过举例ReentrantLock的例子来说明AQS的重要性
ReentrantLock与AQS的关系
ReentrantLock内部维护了一个Sync抽象类继承于AQS,并根据不同的公平机制实现了两个子类FairSync与NonFairSync。
调用lock()方法
调用lock()方法
-
sync.lock()根据是否是公平锁选择不同的子类的具体实现方法NonfairSync.lock()与FairSync.lock() -
acquire(1),子类未重写此方法,调用AQS的方法 -
tryAcquire(arg)根据子类的实现不同调用不同的方法,两个方法不同之处在于FairSync多调用了一个hasQueuedPredecessors()方法,用于查询队列中是否有等待的线程Node,若队列中没有等待的线程,则使用CAS操作去争夺锁;NonFairSync则是直接进行CAS操作去争夺锁 - 若调用
tryAcquire(arg)返回false,则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)将线程放进队列中等待;其中addWaiter()方法操作是将新的node以CAS操作将node加到队列尾部(其中在JDK1.9之后,引入VarHandle来操作将当前Node加在oldTail之后),其中enq()方法是CAS操作失败后,重复使用CAS操作直至成功加入队尾;acquireQueued()方法,是通过再次确认当前线程是否能够获取锁,若不能则执行LockSupport.park()进行当前线程的暂停
varHandle
在JDK1.9之后才出现,可以对普通属性进行原子性操作,且比反射快,可以理解为直接操作二进制码。
调用unlock()方法
-
unlock()方法调用sync.release(1)方法 -
sync.release(1)调用tryRelease(arg)去释放锁,由于锁是可重入的,所以tryRelease()执行结束后不一定释放,所以tryRelease()方法执行完,会更新锁的状态state,若state为0,则会释放当前占有线程 - 释放成功后,判断等待队列中的
head结点,即第一个等待线程,判断其状态waitStatus,若不为0,则执行unparkSuccessor()方法,进行下一个线程的唤醒操作