概览:
- 在并发编程中不得不提到的就是AQS(AbstractQueueSynchronizer)抽象同步队列,它是实现同步器的基础组件,并发包中锁的底层实现就是使用AQS来实现的。AQS的结构图如下:
- 从该图中可以看到AQS是一个
FIFO(先进先出)双向队列
,其内部节点通过head和tail记录队首和队尾元素,队列元素的类型为Node,其中Node中的thread变量用来存放AQS队列中的线程;Node节点内部的SHARED
用来标记该线程是获取共享资源
时被阻塞挂起后放入AQS队列的,EXCLUSIVE
用来标记线程是获取独占资源
时被挂起后放入AQS队列的;waitStatus是记录当前线程的等待状态,可以为CANCELLED(线程被取消)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列中等待)、PROPAGATE(释放共享资源时需要通知其它节点);prev记录当前节点的前驱节点,next记录当前节点的后继节点。 - 在AQS中维护了一个单一的状态信息
state
,可以通过getState、setState、CompareAndSetState函数来修改其值。- 对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的
可重入次数
; - 对于ReentrantReadWriteLock来说,state
高16
为表示读
状态,也就是获取该读锁的次数,低16位
表示获取到写锁
的的线程的可重入次数; - 对于Semaphore来说,state用来表示当前
可用信号
的个数, - 对于CountDownlatch来说,state用来表示
计数器
当前的值。
- 对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的
- AQS有个内部类ConditionObject,用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列,ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的await方法后被阻塞的线程。
- 对于AQS来说,线程同步的关键是对状态值state进行操作,根据state是否属于一个线程,操作state的方式分为
;
。
在独占方式下获取和释放资源的方法为:
void acquire( int arg)
void acquirelnterruptibly(int arg)
boolean release( int arg)
- 使用独占锁方式获取资源是跟线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取了,其它线程再尝试操作state获取资源时发现当前资源不是自己持有的,就会获取失败后被阻塞。比如独占锁ReentrantLock的实现,当一个线程获取了ReentrantLock的锁后,在AQS内部会首先使用CAS操作把state状态值从0变为1,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现锁的持有者是自己,那么就会把state加1,也就是可以设置重入次数,而当另一个线程获取锁时发现自己并不是该锁的持有者就会被放入AQS阻塞队列后挂起。
在共享方式下获取和释放资源的方法为:
void acquireShared(int arg)
void acquireSharedinterruptibly(int arg)
boolean reeaseShared(int arg)
- 对于共享式来说,当多个线程去请求共享资源时通过CAS方式竞争获取资源,当一个线程获取到资源之后,另一个线程再去获取时如果当前资源还能满足它的需要,则当前线程只需要通过CAS方法进行获取即可。比如Semaphore信号量,当一个线程通过acquire()方式获取信号量时,会首先查看当前信号量个数是否满足需要,不满足则把当前线程放入阻塞队列,如果满足则通过自旋CAS获取信号量。
在独占方式下,获取与释放资源的流程如下:
-
当一个线程调用
acquire(int arg)
方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体时设置状态变量state的值,成功则直接返回,失败则将当前线程封装为Node.EXCLUSIVE的Node节点后插入AQS阻塞队列的尾部,并调用LockSupport.park(this)
方法挂起自己。public final void acquire(int arg){ if(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) selfInterrupt(); }
-
当一个线程调用
release(int arg)
方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)
方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire
尝试,看当前状态变量state的值是否满足自己的需要,满足则激活该线程,然后继续向下运行,否则还会被放入AQS队列并挂起。public final boolean release (int arg) { if (tryRelease(arg )) { Node h = head; if (h 1= null && h .waitStatus != 0) unparkSuccessor(h); return true ; } return false; }
-
需要注意的是,AQS类并没有提供可用的tryAcquire和tryRelease方法,正如AQS的锁阻塞和同步器的基础框架一样,tryAcquire和tryRelease需要具体的子类来实现。子类在实现tryAcquire和tryRelease时要根据具体场景使用CAS算法尝试修改state的值,成功则返回true,否则返回false。子类还需要定义在调用acquire和release方法时state状态值的增减代表什么含义。
-
比如继承自AQS实现的独占锁ReentrantLock,定义当state为0时表示锁空闲,不为0时表示锁已经被占用,在重写tryAcquire时,在内部需要使用CAS算法查看当前state是否为0,如果为0则使用CAS设置为1,并设置锁的持有者为当前线程,而后返回true,如果CAS失败则返回false。
在共享方式下获取与释放锁的流程如下:
-
当线程调用
acquireShared(int arg)
获取共享资源时,会首先使用trγAcquireShared
尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为Node.SHARED
的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)
方法挂起自己。public final void acquireShared(int arg){ if(tryAcquireShared(arg)<0) doAcquireShared(arg) }
-
当一个线程调用
releaseShared(int arg)
时会尝试使用tryReleaseShared
操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)
激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryReleaseShared
查看当前状态变量state的值是否满足自己的需要,满足则激活该线程,然后继续向下运行,否则还是放入AQS的阻塞队列并挂起。public final boolean releaseShared(int arg){ if(tryReleaseShared(arg)){ doReleaseShared(); return true; } return false; }
-
同样需要注意的是,AQS类并没有提供可用
tryAcquireShared
和tryReleaseShared
方法,正如AQS是锁阻塞和同步器的基础框架一样,tryAcquireShared和tryReleaseShared需要由具体的子类来实现。子类在实现tryAcquireShared和tryReleaseShared时要根据具体场景使用CAS算法尝试修改state的值,成功则返回true,否则返回false。 -
比如继承自AQS实现的读写锁
ReentrantReadWriteLock
里面的读锁在重写tryAcquireShared时,首先查看写锁是否被其它线程持有,如果是则直接返回false,否则使用CAS递增state的高16位
(在ReentrantReadWriteLock中,state的高16位为读锁的次数。)在重写tryReleaseShared时,其内部使用CAS算法把当前state的值高16位减1,然后返回true,否则返回false。
以上就是自己整理的AQS的一部分知识,AQS的知识点很多慢慢整理。我是会敲代码的汤姆猫!