抽象同步队列AQS

抽象同步队列AQS

(1) AQS——锁的底层支持

AbstractQueuedSynchronizer抽象同步队列简称AQS,它是实现同步器的基础组件,并发包中锁的底层就是使用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用来表示计数器当前的值

AQS有个内部类ConditionObject,用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列。ConditionObject是条件变量,每个条件变量对应一个条件队列(单项链表队列),其用来存放调用条件变量的await方法后被阻塞的线程,如类图所示,这个条件队列的头、尾元素分别为firstWaiter和lastWaiter。

对于AQS来说,线程同步的关键是对状态值state进行操作。根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。

  • 在独占方式下获取和释放资源使用的方法为
    • void acquire(int arg)
    • void acquireInterruptibly(int arg)
    • boolean release(int arg)
  • 在共享方式下获取和释放资源的方法为
    • void acquireShared(int arg)
    • void acquireShareInterruptibly(int arg)
    • boolean releaseShared(int arg)
  • 这两套函数中都有一个带有Interruptibly关键字的函数
    • 不带Interruptibly关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,那么该线程不会因为被中断而抛出异常,它还是继续获取资源或者被挂起,也就是说不对中断进行响应,忽略中断
    • 而带Interruptibly关键字的方法要对中断进行响应,也就是线程在调用带Interruptibly关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,那么该线程会抛出InterruptedException异常而返回

(2) AQS——条件变量的支持

  • notify和wait是配合synchronized内置锁实现线程间同步的基础设施,条件变量的signal和await方法也是用来配合锁(使用AQS实现的锁)实现线程间同步的基础设施。
  • 它们的不同在于,synchronized同时只能对一个与共享变量的notify或wait方法实现同步,而AQS的一个锁可以对应多个条件变量。

条件变量其实就是一个在AQS内部声明的ConditionObject对象,ConditionObject是AQS的内部类,可以访问AQS内部的变量(例如状态变量state)和方法。在每个条件变量内部都维护了一个条件队列,用来存放调用条件变量的await()方法时被阻塞的线程。注意这个条件队列和AQS队列不是一回事。


版权声明:本文为HardyZhan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。