zoukankan      html  css  js  c++  java
  • JAVA并发之AQS概述

    一、前言

      自从看了《java并发编程实战后》,被用来构建锁以及同步工具的框架AQS在我心中是一个很厉害的东西,juc包下的很多工具类都是依靠AQS实现的,比如CountDownLatch、ReentrantLock、Semaphore、Mutex等。(本系列所有源码均来自jdk1.8,书中提到的FutureTask在jdk1.8下并没有依靠AQS实现)。

      记得第一次看AbstractQueuedSynchronizer源码是得知ReentrantLock的时候,秉承着“多了解源码”的教诲,我顺着lock.lock()方法,点开了那个acquire(1),迎面而来的有这种在方法中抛异常的

    1     protected boolean tryAcquire(int arg) {
    2         throw new UnsupportedOperationException();
    3     }

      也有调用到深处是unsafe方法令我一头雾水的

    1     public static void park(Object blocker) {
    2         Thread t = Thread.currentThread();
    3         setBlocker(t, blocker);
    4         UNSAFE.park(false, 0L);
    5         setBlocker(t, null);
    6     }

      然后我默默的关闭了那2000+行的aqs,想尝试用另一种方法阅读源码。

      在翻阅了不少博客以及一遍又一遍的“浏览”AQS,一次次以为自己明白又一次次的推翻自己的猜测后,我开始想把我眼中的AQS类描述出来,在此之前我也有想过在博客上记录我的认知,但是总是担心自己写出来的都是对其的误解,虽然现在依旧有这样的担忧,不过写错了改过就是了,如果有大佬路过随手“点拨”了我一下,那我更是血赚了。

    话不多说,下面从总体上描述AQS。

    二、正文

    1.state共享资源

    1     /**
    2      * The synchronization state.
    3      */
    4     private volatile int state;

      一个volatile修饰的属性代表共享资源保证其对所有线程可见性, 提供get、set、以及compareAndSetState(cas)方法操作共享资源,前言中提到的lock.lokc()中的acquire(1)操作的就是state,在ReentrantLock中state代表线程是否已经获取了锁,0未获取,1已经获取了。

    2.CLH队列(FIFO队列)

      AQS中构造了一种Node用来实现CLH队列

     1     static final class Node {
     2         //Node的模式:共享或独占
     3         static final Node SHARED = new Node();
     4         static final Node EXCLUSIVE = null;
     5 
     6         //因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;
     7         static final int CANCELLED =  1;
     8 
     9         //后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
    10         static final int SIGNAL    = -1;
    11 
    12         //节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,改节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
    13         static final int CONDITION = -2;
    14 
    15         //表示下一次共享式同步状态获取将会无条件地传播下去
    16         static final int PROPAGATE = -3;
    17 
    18         //Node的状态,是以上四种之一
    19         volatile int waitStatus;
    20 
    21         //队列维护双向节点
    22         volatile Node prev;
    23         volatile Node next;
    24 
    25         //获取同步状态的线程
    26         volatile Thread thread;
    27 
    28         Node nextWaiter;
    29         ...省略了方法
    30     }

      以上是Node的主要属性,其实就是一个有waitStatus的节点,配合AQS中的

    1     private transient volatile Node head;
    2     private transient volatile Node tail;

      两个头尾节点,构成了一个FIFO的双端队列,多线程争用资源被阻塞时会进入此队列等待。(此处描述的CLH是AQS中的CLH队列,与其他CLH的实现有些不同)。

      而对于Node模式的理解,ReentrantLock是用独占模式的节点,而ReentrantReadWriteLock中的读锁使用的是共享模式,对于不同的需求选择需要的节点模式。

      3.主要方法

    • public final void acquire(int arg)
    • public final boolean release(int arg)

      我个人认为AQS最主要的方法有两种,一种就是acquire,如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,另一种是与之相反的release,用于释放占有的临界资源。当然还有很多种选择,与acquire类似的有acquireInterruptibly,acquireShared,acquireSharedInterruptibly。看方法名就能大概知道有Interruptibly是与中断相关的,acquire是会忽略中断的,而acquireInterruptibly则会响应中断;shared在上面已经提到过了,是node的一种模式,因此shared是在共享模式下获取资源的方法。同样的release方法也有三个兄弟。

    • protected boolean tryAcquire(int arg) //acquire、acquireInterruptibly所使用的方法
    • protected boolean tryAcquireShared(int arg) //acquireShared、acquireSharedInterruptibly所使用的方法
    • protected boolean tryRelease(int arg)
    • protected boolean tryReleaseShared(int arg)

      根据请求(释放)资源的模式不同,相应的所调用的尝试请求资源方法也不同,例如acquire、acquireInterruptibly都是独占式的申请资源,因此这两个方法中会调用tryAcquire方法。

      需要注意的一点是在AQS中tryAcquire的源码是这样直接抛出一个未支持操作异常

    1     protected boolean tryAcquire(int arg) {
    2         throw new UnsupportedOperationException();
    3     }

      另外三个也是一样的,这是因为一般而言一个同步工具占有资源的模式不是共享的就是独占的,因此借助AQS的开发者只需要实现tryAcquire、tryRelease或tryAcquireShared、tryReleaseShared的一类即可,另一类反正用不到那就不需要更改了,与将其定义成抽象方法相比,可以减轻开发者的负担。

      其他方法在本篇概述中就不细谈了。

      4.其他

      在此简单罗列一些AQS中用到的技术,不详细展开,但是了解这些技术对于源码的阅读会有一定的帮助,因为本人在阅读过程中经常会遇到一些令我费解的代码。

    • CAS操作:compareAndSwap,在代码中主要表现为unsafe.compareAndSwapxxx,这是使用本地方法实现使用乐观锁更新变量值。(jdk1.8中将自旋操作也写在本地方法中,CAS操作更加接近底层)。
    • park操作:LockSupport.park(this),park类似于object.wait(),都是将线程从RUNNABLE状态转变成WAITING状态,不同的是park不会抛出InterruptedException,也就是不会响应Interrupt中断;另外park不一定要用在同步代码块中。

    三、总结

       本文主要粗略描述了AQS的大致内容,在后续中会对AQS中的方法具体流程进行描述,以及结合同步工具是如何使用AQS框架构造的进行一定的分析。本文也将随着我进一步的学习对描述或者认知存在问题的地方进行更改,也很期待下一次的更新。

  • 相关阅读:
    文件数据源设置
    维护0material主数据,提示Settings for material number conversion not found
    7.5版本COPA数据源创建转换提示“不允许对象名称为空”
    COPA指标自动创建
    IDEA操作数据库
    Docker(快速实战流程)
    Docker(理论部分小结)
    Docker数据卷挂载相关
    解决pycharm启动updating Python interpreter长时间更新
    IDEA完美配置(shell)linux的命令行工具
  • 原文地址:https://www.cnblogs.com/zzzdp/p/9123075.html
Copyright © 2011-2022 走看看