深入理解 Java 并发锁(1)-- AQS

一. AQS介绍

全称为AbstractQueuedSynchronizer,其主要作用是处理同步,它是很多并发锁和很多同步工具类的实现基础(例如ReenTrantLock,CountDownLatch,FutureTask等)

1.1 独享锁和共享锁的支持

ReentrantLock、 ReadWriteLock是基于AQS来实现的,这些锁没有直接继承AQS,而是定义了一个Sync类去继承AQS。为什么要这么做呢?因为锁面向的是使用用户,而同步器面向的是线程控制,那么在锁实现中聚合同步器而不是直接继承AQS就可以很好的隔离两者所关注的事情。

1.2 独享锁API

  • acquire - 获取独占锁。
  • acquireInterruptibly - 获取可中断的独占锁。
  • tryAcquireNanos - 尝试在指定时间内获取可中断的独占锁。在以下三种情况下会返回:
    • 在超时时间内,当前线程成功获取了锁;
    • 当前线程在超时时间内被中断;
    • 超时时间结束,仍未获得锁返回 false。
  • release - 释放独占锁。

1.3 共享锁APi

  • acquireShared - 获取共享锁。
  • acquireSharedInterruptibly - 获取可中断的共享锁。
  • tryAcquireSharedNanos - 尝试在指定时间内获取可中断的共享锁。
  • release - 释放共享锁。

二. AQS原理

在AQS中使用了一个由volatile修饰的变量state,用来维护同步状态,状态的意义由子类赋予。

AQS维护了一个FIFO的双向链表,用来存储获取锁失败的线程。

AQS主要围绕着两种状态:获取和释放,有以下几点:

  1. state是独占的,还是共享的
  2. state被获取后,其他线程需要等待
  3. state被释放后,唤醒等待线程
  4. 线程等不及时,如何退出等待

至于线程是否可以获得state,如何释放state,不是AQS所关注的,由子类具体实现,如ReentrantLock中该状态值表示所有者线程已经重复获取该锁的次数,Semaphore中该状态值表示剩余的许可数量。

2.1 AQS的数据结构

通过阅读源码可知,AQS继承自AbstractOwnableSynchronizer

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

    /** 等待队列的队头,懒加载。只能通过 setHead 方法修改。 */
    private transient volatile Node head;
    /** 等待队列的队尾,懒加载。只能通过 enq 方法添加新的等待节点。*/
    private transient volatile Node tail;
    /** 同步状态 */
    private volatile int state;
}

head与tail就是AQS维护的一个双向链表,每当有线程获取锁失败后,就被添加到队列末尾。

然后看一些内部链表的源码:

static final class Node {
    /** 该等待同步的节点处于共享模式 */
    static final Node SHARED = new Node();
    /** 该等待同步的节点处于独占模式 */
    static final Node EXCLUSIVE = null;

    /** 线程等待状态,状态值有: 0、1、-1、-2、-3 */
    volatile int waitStatus;
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    /** 前驱节点 */
    volatile Node prev;
    /** 后继节点 */
    volatile Node next;
    /** 等待锁的线程 */
    volatile Thread thread;

  	/** 和节点是否共享有关 */
    Node nextWaiter;
}

其中,有个非常重要的属性:waitStatus,这是链表用于维护AQS队列中线程节点的状态,具有以下值:

CANCELLED(1):该节点的线程可能由于超时或者被中断而处于被取消的状态,一旦处在这个状态,表示这个节点应该从等待队列中移除。

SIGNAL(-1):此状态表示:后继节点会被挂起,因此在当前节点释放锁或者被取消后,必须唤醒其后继节点。

CONDITION(-2):该节点的线程处于等待条件状态,不会被当作是同步队列上的节点,直到被唤醒,设置其值为0,再重新进入阻塞状态。

PROPAGATE(-3):此状态表示下一个获取共享锁(acquireShared)应无条件传播。

0:非以上状态。

2.2 独占锁的获取和释放

2.2.1 获取

AQS使用acquire()方法获取独占锁,大致流程如下:

  1. 先尝试获取同步状态,如果获取同步状态成功,则结束方法,直接返回。
  2. 如果获取同步状态不成功,AQS会不断尝试利用CAS操作将当前线程插入等待同步队列对队尾,直到成功为止。
  3. 接着,不断尝试为等待队列中的线程节点获取独占锁。

2.2.2 释放

AQS使用release()方法获取独占锁,大致流程如下:

  1. 先尝试获取解锁线程的同步状态,如果获取同步状态不成功,结束方法,直接返回
  2. 如果获取同步状态成功,AQS会尝试唤醒当前线程节点的后继节点
2.2.3 获取可中断的独占锁

AQS 中使用 acquireInterruptibly()方法获取可中断的独占锁。

acquireInterruptibly实现方式相较于获取独占锁方法acquire非常相似,区别仅在于它会通过Thread.interrupted检测当前线程是否被中断,如果是,则立即抛出中断异常。

2.2.4 获取超时等待式的独占锁

AQS 中使用 tryAcquireNanos(int arg) 方法获取超时等待的独占锁。

doAcquireNanos 的实现方式 相较于获取独占锁方法acquire非常相似,区别在于它会根据超时时间和当前时间计算出截止时间。在获取锁的流程中,会不断判断是否超时,如果超时,直接返回 false;如果没超时,则用 LockSupport.parkNanos 来阻塞当前线程。

2.3 共享锁的获取和释放

2.3.1 获取共享锁

AQS中使用acquireShared() 方法获取共享锁。

acquireShared方法和acquire方法逻辑相似,区别仅在于自旋的条件以及节点出队的操作有所不同:

成功获取共享锁的条件:

tryAcquireShared方法返回值大于等于0;

当前节点的前驱节点是头节点。

2.3.2 释放共享锁

AQS 中使用 releaseShared(int arg) 方法释放共享锁。

releaseShared 首先会尝试释放同步状态,如果成功,则解锁一个或多个后继线程节点。释放共享锁和释放独享锁流程大体相似,区别在于:

对于独享模式,如果需要 SIGNAL,释放仅相当于调用头节点的 unparkSuccessor。

2.3.3 获取可中断的共享锁

AQS 中使用 acquireSharedInterruptibly(int arg) 方法获取可中断的共享锁。

acquireSharedInterruptibly 方法与 acquireInterruptibly 几乎一致,不再赘述。

2.3.4 获取超时等待式的共享锁

AQS 中使用 tryAcquireSharedNanos(int arg) 方法获取超时等待式的共享锁。

tryAcquireSharedNanos 方法与 tryAcquireNanos 几乎一致,不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值