AQS AbstractQueueSynchronized 简称AQS
是什么:抽象的队列同步器
是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石
通过内置的FIFO队列完成资源获取现成的排队工作,并通过int类变量标识持有锁的状态
AQS为什么是JUC内容中最重要的基石?
ReentrantLock CountDownLatch ReentrantReadWriteLock Semaphore … 底层都是AQS
锁,是面向锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节
同步器,面向锁的实现者,,比如java并发大神DouLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理,阻塞线程排队和通知、唤醒机制等。
加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要队列》
抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。
抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去侯客区排队等候,)但等候的线程仍然保留获取锁的可能且获取锁流程仍然在继续(侯客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)
CLH队列(FIFO)
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的节点Node,通过CAS、自旋以及LockSupport.park() 的方式维护state变量的状态,使并发达到同步的效果。
AQS使用一个volatile的Int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成state值的修改
内部类Node(Node类在AQS类内部)
Node的int变量:
Node的等待状态waitState成员变量
内部结构:
属性说明:
AQS中的队列是CLH变体的虚拟双向队列。
Lock接口的实现类,基本上都是通过聚合了一个队列同步器的子类完成线程访问控制的
公平锁和非公平锁:
可以很明显的看出公平锁和非公平锁的lock() 方法唯一的区别就是在于公平锁在获取同步状态时多了一个限制条件:
hasQueuedPredecessors()
hasQueuedPredecessors() 是公平锁加锁时判断等待队列中是否存在有效节点的方法
对比公平锁和非公平锁的tryAcquire方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断
!hasQueuedPredecessors()
hasQueuedPredecessors 中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
公平锁:
公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程等待,那么当前线程就会进入等待队列中。
非公平锁:
不管是否有等待队列,如果可以获取锁,则立刻占有锁对象,也就是说队列的第一个排队线程在unpark(),之后还是需要竞争锁(存在线程竞争的情况下)
非公平锁开始突破:
lock()方法:
acquire()方法:
tryAcquire(arg)方法:
nonfairTryAcquire(acquires) 方法:
return false 继续推进条件,走下一个方法
return true 结束
addWaiter(Node.EXCLUSIVE) 方法:
addWaiter(Node mode)
end(node)
双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法:
假如再抢抢失败就会进入
shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 方法中
shouldParkAfterFailedAcquire
如果前驱节点的 waitStatus 是 SIGNAL状态,即 shouldParkAfterFailedAcquire 方法会返回 true 程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起
parkAndCheckInterrupt 方法:
unlock 方法:
sync.release(1);-------> tryRelease(arg)-------->unparkSuccessor