乐趣区

关于java:java开发juc并发之AQS入门

AQS 全称:AbstractQueuedSynchronizer,它是 java 培训学习中 JUC 并发工具包中 ReentrantLock、CountDownLatch、CyclicBarrier 等这些类的底层实现。此篇咱们次要通过 ReentrantLock 的应用来大体理解 AQS 底层代码设计原理,不对源码具体赘述。只有对整体的设计方向有清晰理解,再去追踪源码便不是什么难事。浏览本篇之前,须要大家对 ReentrantLock 的 API 有肯定理解。
ReentrantLock 的 lock、await 办法底层逻辑
在应用 ReentrantLock 进行代码块同步,个别都会有如下写法:
1ReentrantLock lock = new ReentrantLock(true);
2Condition condition = lock.newCondition();
3
4try{
5 lock.lock();
6 //…..
7 condition.await();
8
9 }finally{
10 lock.unlock();
11 }
假如 T1 时刻有 10 个线程调用同一个 ReentrantLock 实例的 lock()办法,线程 1 先获取锁胜利,紧接着线程 2 调用 lock()办法,发现获取锁失败(通过 CAS 操作对状态位进行标记),则线程 2 被封装成 Node 节点放入 AQS 双向队列中,并调用 LockSupport.park()阻塞挂起。同样的线程 3,线程 4,…线程 10 也阻塞挂起退出队列中。至此在 t1 时刻 AQS 的队列如图 1 -1:

图 1 -1
T4 时刻线程 1 执行结束,调用 unlock()办法开释锁并从 AQS 队列中取出哨兵节点的下一节点,也就是此刻的线程 2,并唤醒该线程。线程 2 再次尝试获取锁,如果锁获取胜利将继续执行线程 2 代码。如果失败会被再次封装成 Node 节点放入 AQS 队列中去。看到这里大家可能会有疑难:为什么线程 1 的锁开释完后唤醒的是线程 2,而不是线程 3 或者线程 4 呢?这里是因为偏心锁与非偏心锁的起因,如果采纳了偏心锁那么将依照先进先出的准则让线程去抢占锁,而非偏心锁没有先后顺序的限度。对于 ReentrantLock 能够通过构造函数的参数进行指定,默认它采纳的是非偏心锁。
Lock 与 Condition 联合应用又是什么样的原理呢?
咱们紧接着下面的剖析,线程 T1 时刻获取锁胜利后,在 T2 时刻调用了 condition.await()办法时会产生以下 4 件事件:

  1. 开释锁(通过 CAS 操作对状态为进行标记)
  2. 阻塞挂起线程 1
  3. 线程 1 封装成 Node 节点放入条件队列中(留神:此处条件队列和 AQS 队列不是一回事)
  4. 唤醒 AQS 队列中阻塞挂起的线程

当其余线程调用 condition.signal() 或者 condition.sigalAll() 办法时,会将条件队列的一个或者全副 Node 节点移到 AQS 队列中。
一个锁对应一个 AQS 队列、多个 condition、多个条件队列,条件队列的个数是追随 Condition 走的。咱们通过 1 - 2 图来更直观意识 Lock、Condition、AQS 队列、条件队列之间的关系。

作者:小江

退出移动版