并发基础之AQS
Posted 小沈同学呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发基础之AQS相关的知识,希望对你有一定的参考价值。
目录
什么AQS
AQS是位于java.util.concurrent.locks 包下的 AbstractQueuedSynchronizer类,是构建同步器和锁的基础框架。我们可以通过继承AbstractQueuedSynchronizer 创建自己的同步器。比如我们常用的JUC并发包下面的CountDownLatch、ReentrantLock、Semaphore等等都是源于AQS实现的并发工具类。
AQS原理
核心思想是多个线程访问共享资源,如果一个线程发现共享资源没有被占用,则将资源设定为锁定状态且当前线程设为占用线程,如果一个线程发现共享资源已经被占用,则需要等待占用线程释放资源,当然当占用线程释放资源的时候AQS会通知其他等待线程进行资源抢占。在AQS实际源码中是通过state 状态在标识资源是否占用,通过CLH FIFO虚拟双向队列来实现将最近的等待线程放在队列末尾。
重点1:CLH队列
如图所示CLH是一个虚拟的双向队列,是通过state的值实现线程等待排序在队列末尾。队列head 头部是当前资源占用工作线程,node1-node n 则是资源被锁定后等待的线程节点。在实际的资源占用过程中,根据是否公平锁来确定是顺序占用资源和抢占式占用资源。
重点2:state 状态
如上图所示,线程获取资源是否被占用是通过获取 state 状态来实现。AQS源码已经给出获取、设置、通过CAS修改state 的方法。此时我们可以理解为:
1、线程获取state 发现为 0 ,说明没有线程占用则占用该资源,并通过CAS将state 置为 1
2、如果该线程有重入的情况则继续增加state 的值,重入几次就增加几次
3、线程出方法则需要释放state值,也就是减少state
4、当state值为0 的时候标识线程执行完毕解除占用,此时AQS唤醒其他线程可以占用资源
AQS 两种资源共享方式
1) Exclusive(独占)
只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁。根据ReentrantLock定义,我们可知公平锁是根据队列的等待顺序占用资源,非公平锁是所有的等待线程抢占资源。
2) Share(共享)
运行多个线程对资源共享,比如JUC并发工具CountDownLatch、Semaphore等都是运行多个线程访问资源。
AQS 模板方法运用
AQS内置很多的操作方法,比如获取锁 tryAcquire()、释放锁 tryRelease()、超时获取tryAcquireNanos()、响应中断 acquireInterruptibly()等操作方法
在实际的运用过程中,我们可以直接继承AQS从而轻易的获取这些方法来实现我们的同步器。
当前JUC下的并发工具类都是基于AQS来实现自己的功能的,我们现在来鉴赏一下。
比如可重入锁ReentrantLock,我们进入源码查看,ReentrantLock内部类继承AQS:
我们ReentrantLock超时获取锁/释放锁也都是调用AQS超时获取锁/释放锁的方法:
当然其他对资源状态和队列的操作方法都是基于AQS实现的,对于JUC中其他的并发工具类也是如此,都是基于AQS实现。
补充知识CAS
CAS是Compare and Swap 的缩写,也就是比较和替换,是JUC最核心最基础的理论。原理是基于三个数据 主存值V、线程副本值A、需要修改为值B。当且仅当主存值V等于线程副本值A的时候,才能将数据修改为B。
CAS的优点
CAS是乐观锁,且一直自旋等待锁,所以性能很高。
CAS的缺点
ABA问题,如果我们先将值C修改为D,再修改为C,CAS会认为此数据没有修改。
缺点解决办法
1、加版本号
2、JAVA JUC atomic下提供AtomicStampedReference包装类
总结
AQS是位于java.util.concurrent.locks 包下的 AbstractQueuedSynchronizer类,是构建同步器和锁的基础框架。JAVA JUC并发包下的工具类都是基于AQS实现,其原理都是通过state状态来确定线程是否占用资源,未拿到锁的线程则放置在CLH虚拟双向队列末尾,后续通过AQS通知抢占资源。
以上是关于并发基础之AQS的主要内容,如果未能解决你的问题,请参考以下文章