实战:深入AQS大解析!

作者: java菲菲 | 来源:发表于2019-09-27 15:46 被阅读0次

    01 前言

    前段时间在面试,发现面试官都有问到同步器AQS的相关问题。AQS为Java中几乎所有的锁和同步器提供一个基础框架,派生出如ReentrantLock、Semaphore、CountDownLatch等AQS全家桶。本文基于AQS原理的几个核心点,谈谈对AbstractQueuedSynchronizer的理解,并实现一个自定义同步器。

    02 AQS原理面试题的核心回答要点

    1. state 状态的维护。

    2. CLH队列

    3. ConditionObject通知

    4.模板方法设计模式

    5.独占与共享模式。

    6.自定义同步器。

    7. AQS全家桶的一些延伸,如:ReentrantLock等。

    03  AQS的类图结构

    AQS全称是AbstractQueuedSynchronizer,即抽象同步队列。下面看一下AQS的类图结构:

    为了方便下面几个关键点的理解,大家先熟悉一下AQS的类图结构

    1. state 状态的维护

    在AQS中维持了一个单一的共享状态state,来实现同步器同步。看一下state的相关代码如下:复制代码

    (1)state源码

    (2)state 源码设计几个回答要点

    ① state用volatile修饰,保证多线程中的可见性。

    ② getState()和setState()方法采用final修饰,限制AQS的子类重写它们两。

    ③ compareAndSetState()方法采用乐观锁思想的CAS算法,也是采用final修饰的,不允许子类重写。

    2. CLH队列

    谈到CLH队列,我们结合以上state状态,先来看一下AQS原理图

    CLH(Craig, Landin, and Hagersten locks) 同步队列是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。AQS依赖它来完成同步状态state的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

    (1)Node节点

    CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),condition队列的后续节点(nextWaiter)如下图:

    waitStatus几种状态状态:

    我们再看一下CLH队列入列以及出列的代码:

    (2)入列

    CLH队列入列就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。addWaiter方法如下:

    由以上代码可得,addWaiter设置尾节点失败的话,调用enq(Node node)方法设置尾节点,enq方法如下: 

    (3)出列

    首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点。可以看一下以下两段源码:

    (4)CLH核心几个回答要点

    ① 双向链表入列出列

    ② CAS算法设置尾节点+死循环自旋。

    3. ConditionObject

    (1)ConditionObject简介

    我们都知道,synchronized控制同步的时候,可以配合Object的wait()、notify(),notifyAll()系列方法可以实现等待/通知模式。而Lock呢?它提供了条件Condition接口,配合await(),signal(),signalAll()等方法也可以实现等待/通知机制。ConditionObject实现了Condition接口,给AQS提供条件变量的支持

    (2)Condition队列与CLH队列的那些事

    我们先来看一下图:

    ConditionObject队列与CLH队列的爱恨情仇:

    ① 调用了await()方法的线程,会被加入到conditionObject等待队列中,并且唤醒CLH队列中head节点的下一个节点。

    ② 线程在某个ConditionObject对象上调用了singnal()方法后,等待队列中的firstWaiter会被加入到AQS的CLH队列中,等待被唤醒。

    ③ 当线程调用unLock()方法释放锁时,CLH队列中的head节点的下一个节点(在本例中是firtWaiter),会被唤醒。

    区别:

    ① ConditionObject对象都维护了一个单独的等待队列,AQS所维护的CLH队列是同步队列,它们节点类型相同,都是Node。

    04 独占与共享模式。

    AQS支持两种同步模式:独占式和共享式。

    1. 独占式

    同一时刻仅有一个线程持有同步状态,如ReentrantLock。又可分为公平锁和非公平锁。

    公平锁:按照线程在队列中的排队顺序,有礼貌的,先到者先拿到锁。

    非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,不讲道理的,谁抢到就是谁的。

    acquire(int arg)是独占式获取同步状态的方法,我们来看一下源码:

    ① acquire(long arg)方法


    ② addWaiter方法

    ③ acquireQueued(final Node node, long arg)方法

    ④ selfInterrupt()方法

    结合源代码,可得acquire(int arg)方法流程图,如下:

    2. 共享式

    多个线程可同时执行,如Semaphore/CountDownLatch等都是共享式的产物。

    acquireShared(long arg)是共享式获取同步状态的方法,可以看一下源码:

    由上可得,先调用tryAcquireShared(int arg)方法尝试获取同步状态,如果获取失败,调用doAcquireShared(int arg)自旋方式获取同步状态,方法源码如下:

    05 AQS的模板方法设计模式

    1. 模板方法模式

    模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

    模板方法模式生活中的例子:假设我们要去北京旅游,那么我们可以坐高铁或者飞机,或者火车,那么定义交通方式的抽象类,可以有以下模板:买票->安检->乘坐xx交通工具->到达北京。让子类继承该抽象类,实现对应的模板方法。

    AQS定义的一些模板方法如下:

    ① isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。

    ② tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。

    ③ tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

    ④ tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

    简言之,就是AQS提供tryAcquire,tryAcquireShared等模板方法,给子类实现自定义的同步器

    06 自定义同步器。

    基于以上分析,我们都知道state,CLH队列,ConditionObject队列等这些关键点,你要实现自定义锁的话,首先需要确定你要实现的是独占锁还是共享锁,定义原子变量state的含义,再定义一个内部类去继承AQS,重写对应的模板方法

    我们来看一下基于 AQS 实现的不可重入的独占锁的demo:

    NonReentrantLockDemoTest:

    运行结果:

    07 AQS全家桶实战

    AQS派生出如ReentrantLock、Semaphore等AQS全家桶,接下来可以看一下它们的使用案例。

    1.ReentrantLock

    (1)ReentrantLock介绍

    ① ReentrantLock为重入锁,能够对共享资源能够重复加锁,是实现Lock接口的一个类。

    ② ReentrantLock支持公平锁和非公平锁两种方式

    (2)ReentrantLock案例

    使用ReentrantLock来实现个简单线程安全的list,如下:

    2. Semaphore

    (1)Semaphore介绍

    Semaphore也叫信号量,可以用来控制资源并发访问的线程数量,通过协调各个线程,以保证合理的使用资源。

    (2)Semaphore案例

    Java多线程有一到比较经典的面试题:ABC三个线程顺序输出,循环10遍。

    相关文章

      网友评论

        本文标题:实战:深入AQS大解析!

        本文链接:https://www.haomeiwen.com/subject/htkauctx.html