Java并发J.U.C 之 AQS

J.U.C 之 AQS

AbStractQueuedSynchronizer类,简称 AQS,是一个来构建锁和同步器的框架, JDK1.5开始引入了 J.U.C,大大提高了JAVA程序的并发性,而 AQS则是 J.U.C的核心,是并发类中的核心部分,他是一个基于 FIFO队列,这个队列可以构建锁或其它相关的同步基础框架

AQS底层结构

image-20210111211406234

底层采用双向链表,是队列的一种实现,因此可以当做是一个队列。其中Sync queue即同步队列,它是双向链表,包括hean结点(主要用作后续的调度)与tail结点。Condition queue不是必须的,单向链表,只有在需要使用到condition的时候才会存在这个单向链表,并且可能存在多个Condition queue
  • 使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架
  • 利用了一个int类型表示状态。在AQS中,存在一个state成员变量,基于AQS有一个同步组件ReentrantLock,在这个组件中,state表示获取锁的线程数,假如state == 0表示无线程获取锁,state == 1表示已有线程获取锁,state > 1表示锁的数量
  • 使用方法是继承。AQS的设计是基于模板方法,使用需要继承AQS,并覆写其中的方法。
  • 子类通过继承并通过实现它的方法管理其状态{acquire() 和 release()}的方法操纵状态
  • 可以同时实现排它锁和共享锁模式(独占、共享)。它的所有子类中,要么实现并使用它的独占功能API,要么实现共享锁的功能,而不会同时使用两套API。即便是它比较有名的子类ReentrantReadWirteLock也是通过两个内部类读锁和写锁分别使用两套API实现的。AQS在功能上,有独占控制和共享控制两种功能。
  • 在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建.然而这些锁都没有直接来继承AQS,而是定义了一个Sync类去继承AQS,因为锁面向的是使用用户,而同步器面向的则是线程控制,那么在锁的实现中聚合同步器而不是直接继承AQS就可以很好的隔离二者所关注的事情.

基于以上设计,AQS具体实现的大致思路

AQS内部维护了一个CLH队列来管理锁,线程首先会尝试获取锁,如果失败,会将当前线程以及等待状态等信息包装成Node结点加入同步队列(Sync queue)中。接着不断循环尝试获取锁,条件是当前结点为head直接后继才会尝试,如果失败则会阻塞自己,直到自己被唤醒;而当持有锁的线程,释放锁的时候,会唤醒队列中后继线程。基于这些基础的设计和思路,JDK提供了许多基于AQS的子类。

独占式锁过程总结

AQS的模板方法acquire通过调用子类自定义实现的tryAcquire获取同步状态失败后->将线程构造成Node节点(创建一个独占式节点 )(addWaiter)->将Node节点添加到同步队列对尾(addWaiter)->节点以自旋的方法获取同步状态(acquirQueued)。在节点自旋获取同步状态时,只有其前驱节点是头节点的时候才会尝试获取同步状态,如果该节点的前驱不是头节点或者该节点的前驱节点是头节点单获取同步状态失败,则判断当前线程需要阻塞,如果需要阻塞则需要被唤醒过后才返回。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点。

共享式锁过程总结

共享式获取与独占式获取的最主要区别在于同一时刻能否有多个线程同时获取到同步状态。通过调用acquireShared(int arg)方法可以共享式得获取同步状态。

同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,其返回值为int类型,当返回值大于0时,表示能够获取同步状态。因此,在共享式获取的自旋过程中,成功获取同步状态并且退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。共享式释放同步状态状态是通过调用releaseShared(int arg)方法

CountDownLatch、ReentrantReadWriteLock、Semaphore等都是共享式获取同步状态的。

关注微信公众号:【入门小站】解锁更多知识点

你可能感兴趣的:(java,多线程)