AQS原理和队列锁机制(学习笔记三)
AQS的理解
AQS是队列同步器AbstractQueuedSynchronizer的简称,它是用来构建锁或其他同步组件的基础框架,内部有一个int型变量state来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
使用和设计模式
AQS主要通过继承并实现它的抽象方法来管理同步状态,推荐使用自定义同步组件的静态内部类来继承,可以使用AQS提供的三个能保证状态安全的方法来进行操作getState、setState(int)、compareAndSetState(int expect,int update)
锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节,而同步器是面向锁的实现者的,它简化了锁的实现方式,屏蔽了同步状态管理,线程排队,等待与唤醒等底层操作。锁和同步器很好的隔离了使用者和实现者锁需要关注的领域
模板设计模式
同步器的设计基于模板方法模式,模板方法模式的意图是,定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
实现者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
AQS中 方法
模板方法
这些模板方法同步器提供的模板方法基本上分为3类:独占式获取与释放同步状态、共享式获取与释放、同步状态和查询同步队列中的等待线程情况
可重写的方法
访问或修改同步状态的方法
重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态。
- getState():获取当前同步状态。
- setState(int newState):设置当前同步状态。
- compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。
CHL队列锁
在java中AQS是基于CHL的变体实现,CLH队列锁即Craig, Landin, and Hagersten (CLH) locks。
CLH队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。
当一个线程需要获取锁时:
-
创建一个队列节点,将其中locked设置为true,表示需要获取锁,myPred表示对其前驱结点的引用
-
线程A对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前驱结点的引用myPred
线程B需要获得锁,同样的流程再来一遍
-
线程就在前驱结点的locked字段上旋转,直到前驱结点释放锁(前驱节点的锁值 locked == false)
-
当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前驱结点
如上图所示,前驱结点释放锁,线程A的myPred所指向的前驱结点的locked字段变为false,线程A就可以获取到锁
CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail)。CLH队列锁常用在SMP体系结构下
小结
AQS与CHL不同之处还体现在链表结构,AQS是双链表结构,并且不会自旋很多次,执行两三次CAS不成功就会阻塞线程,以此来权衡性能。