AQS的全称是AbstractQueuedSynchronizer,这是AQS框架的核心抽象类。ReentrantLock有三个内部类:Sync、NonfairSync、FairSync。FairSync代表了公平锁,NonfairSync代表了非公平锁,NonfairSync和FairSync都继承自Sync,Sync继承自AbstractQueuedSynchronizer。

       AQS源自Object的wait/notify,所以先要理解Object的wait/notify。


一、synchronized-wait-notify机制

       wait():当前线程先要用synchronized获取object或class的监视器锁(后文都简称锁),成功获得锁后,才能调用wait()。wait()此时至少会做三件事:把当前线程放入等待锁的集合、释放锁、当前线程进入阻塞状态。得到通知后,线程被唤醒,wait()继续执行,wait() 再次尝试获得锁,获锁之后才能成功退出wait() 。

        notify():当前线程先要获取锁,成功获得锁后,才能执行notify()方法,notify()方法从等待锁的集合中随机选一个线程唤醒。当前线程释放锁后,才能真正执行唤醒。

wait()和notify()都必须用同一把锁。

上面涉及两个集合:等待锁释放的线程集合,等待被唤醒的线程集合。

       AQS框架取代了synchronized/wait/notify,把上述两个集合显示化。

   

二、AQS类结构简介

       要理解AQS框架,必然要理解代码逻辑,这里简单讲下类的结构。

       AbstractQueuedSynchronizer有四个关键实例变量:

       ① private volatile int state:代表当前线程进入锁的重入数。

       ② private transient Thread exclusiveOwnerThread:继承自AbstractOwnableSynchronizer,代表当前获得锁的线程。

       ③ private transient volatile Node head:同步队列的头节点

       ④ private transient volatile Node tail:同步队列的尾节点

       ConditionObject是AbstractQueuedSynchronizer的内部类,定义了条件队列和等待通知机制。

       Node也是AbstractQueuedSynchronizer的内部类,代表了代表同步队列和条件队列的节点。Node有前驱引用和后驱引用,所以同步队列和条件队列都是双向链表。Node有个重要的实例变量waitStatus,和通知机制有关。


三、ReentrantLock加锁主流程

1、公平锁:第一个线程直接获得锁,state从0变为1,不会构建同步队列,exclusiveOwnerThread被赋值为第一个线程的引用,state被赋值为1。第二个线程如果第一次获得锁失败,则会构建同步队列,队列有两个节点,第一个是虚节点代表获得锁的线程,第二个节点封装第二个线程。第二次获得锁失败则会把第一个线程的(前一个队列节点)waitStatus置为-1,代表第一个线程释放锁之后需要通知并唤醒后续节点,然后进入blocking状态。第三个线程如果获得锁失败,则new一个Node节点封装第三个线程加入同步队列,把前一个节点的waitStatus置为-1。

2、非公平锁:先尝试直接获取锁,而不管同步队列。获取失败才会加入同步队列。后续流程同公平锁。


四、ReentrantLock解锁主流程

      公平锁和非公平锁的流程是一样的,都是同步队列的头节点出队,并唤醒第二个节点加锁。如果第二个节点被取消,则转为从尾部开始找阻塞的节点。


五、Condition的await()主流程

       await()首先判断当前线程是不是已占住锁,占住锁才能继续。然后new一个新的Node实例,封装当前线程,waitStatus的值变为Node.CONDITONAL,该实例加入条件队列尾部。然后释放锁,再调用LockSupport.park()进入阻塞状态。

       在当前线程释放锁之前,当前线程必然是同步队列的第一个节点。释放锁之后,当前线程就从同步队列出队了。

       被唤醒后,再次尝试获取锁。获取锁之后退出await()方法。


六、Condition的 signal()主流程

       signal()首先判断当前线程是不是已占住锁,占住锁才能继续。然后把条件队列首节点的waitStatus置为0,然后该首节点从条件队列出队。出队后的节点不会被垃圾回收,而是加入同步队列尾部,把同步队列的倒数第二个节点的waitStatus置为-1,再用LockSupport.unpark()唤醒出队节点代表的线程。


上述分析忽略了响应中断的流程。欢迎讨论、质疑、指正。