轻松理解AQS框架 |不会有人看了不懂吧

本文作者:可乐可乐可,博主个人主页:可乐可乐可的个人主页

轻松理解AQS框架

本文需要以下知识铺垫:Java、临界区、信号量、锁

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java中重入锁ReentrantLock、读写锁、信号量的实现基石。

学会、了解AQS框架对了解Java锁有很大的帮助

说的比唱的好听,AQS源码下来2k+行,这是人干的活吗?

为了解决大家AQS不了解以及看了忘,忘了看的恶性循环,下面将带领大家从简到繁,一步步的学会AQS框架。

本文中涉及的代码以及做好了中文的注释,带伙可以访问我的github仓库拉下来看

github仓库地址:Jirath-Liu

AQS是啥

各位Java开发者必然会了解一个类,叫ReentrantLock。

在早期使用ReentrantLock效率是远远超过synchronized关键字的,现在差距一步步缩小了。

不知道有没有人点开过ReentrantLock的源码一探究竟?

ReentrantLock内部真正起作用的是Sync类,ReentrantLock所有跟锁有关的方法都调用了Sync的方法来实际实现。

而Sync的父类正是我们的主角——AbstractQueuedSynchronizer

image-20210221221321504

public void lock() {
   
    sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
   
        sync.lockInterruptibly();
}
public boolean tryLock() {
   
        return sync.tryLock();
}
....

关于ReentrantLock这里就不在啰嗦了,如果有想了解的可以留言,给大伙安排上

我们现在关心的,是里面的这个Sync——AQS的实现类

Sync,或者说AQS的实现类,究竟做了什么,达到了加锁的目的?

加锁大家应该都知道是什么概念吧,信号想必比各位也应该了解(不清楚的先去搜搜

加锁的实质就是信号量,若有线程占用了某个资源,就在信号量进行标记,其他线程就了解这段临界区是被占用的。

我们AQS的原理其实就是信号量机制,Sync的机制如下图

image-20210221222707969

这其中的过程都是由AQS来实现的,Sync编写了一些核心的判断来定制。

image-20210221222957211

上图为Sync的源码,acquire是AQS的方法。

扯了这么半天,想必对AQS有个模糊的认识了:

一个实现了信号量,等待,抢夺锁的轻量级框架。

AbstractQueuedSynchronizer


提供一个框架来实现依赖于先进先出(FIFO)等待队列的阻塞锁相关的同步器(信号量,事件等)。

此类旨在为大多数依赖单个原子int值表示状态的同步器提供有用的基础。

子类必须定义更改此状态的受保护方法,并定义该状态对于获取或释放此对象而言意味着什么。

如何使用AQS来构建自己的锁?

我们先学会用AQS,再探知AQS的原理,总得先会跑,再想怎么跑步省力气吧

AQS框架的大佬给我们提供了四个需要我们实现的接口:

image-20210221223712534

这几个方法都默认直接抛出异常:UnsupportedOperationException,需要子类继承来重写。

这四个方法都是干啥的?

AQS使用了模板方法的设计模式,这四个方法除了编写后直接使用外,更会被框架的其他方法调用。只要我们按照规矩老老实实的编写好这四个方法,就能得到一个出自自己之手的高效的轻量的锁。

这四个方法的功能就在下面了

// Main exported methods

/**
 * 尝试以独占模式进行获取。
 *
 * 此方法应查询对象的状态是否允许以独占模式获取它,如果允许则获取它。
 * 此方法始终由执行获取的线程调用。
 * 如果此方法报告失败,则acquire方法可以将线程排队(如果尚未排队),
 * 直到其他某个线程释放该信号为止。
 *
 * 这可以用来实现方法Lock.tryLock() 。
 * 默认实现抛出UnsupportedOperationException 。
 */
protected boolean tryAcquire(int arg) {
   
    throw new UnsupportedOperationException();
}

/**
 * 尝试设置状态以反映排他模式下的发布。
 * 始终由执行释放的线程调用此方法。
 * 默认实现抛出UnsupportedOperationException 。
 */
protected boolean tryRelease(int arg) {
   
    throw new UnsupportedOperationException();
}

/**
 * 尝试以共享模式进行获取。
 * 此方法应查询对象的状态是否允许以共享模式获取对象,如果允许则获取对象。
 * 此方法始终由执行获取的线程调用。
 * 如果此方法报告失败,则acquire方法可以将线程排队(如果尚未排队),直到其他某个线程释放该信号为止。
 * 默认实现抛出UnsupportedOperationException 。
 */
protected int tryAcquireShared(int arg) {
   
    throw new UnsupportedOperationException();
}

/**
 * 尝试设置状态以反映共享模式下的发布。
 * 始终由执行释放的线程调用此方法。
 * 默认实现抛出UnsupportedOperationException 。
 */
protected boolean tryReleaseShared(int arg) {
   
    throw new UnsupportedOperationException();
}

/**
 * 如果仅相对于当前(调用)线程保持同步,则返回true 。
 * 每次调用非等待的AbstractQueuedSynchronizer.ConditionObject方法时,都会调用此方法。
 * (等待方法改为调用release 。)
 * 默认实现抛出UnsupportedOperationException 。
 * 此方法仅在AbstractQueuedSynchronizer.ConditionObject方法内部内部调用,
 * 因此如果不使用条件,则无需定义。
 *
 */
protected boolean isHeldExclusively() {
   
    throw new UnsupportedOperationException();
}

当然,只拿了这四个方法,肯定是一脸懵逼的,AQS框架提供了很多的方法供子类使用,这些方法都是模板方法,final类型

image-20210222093629615

image-20210222092649114

大致有这些方法:

  1. 获取独占锁,以及各种姿势来获取(超时,响应中断,尝试等等),这些方法命名为acquire
  2. 获取共享锁,以及各种姿势来获取(超时,响应中断,尝试等等),这些方法命名为acquireShared
  3. 释放锁,包括释放独占锁和释放共享锁,释放锁是不处理线程争夺问题的
  4. 对等待(Wait)的操作
  5. 对AQS的感知,
    1. 是否有排队,目标线程是不是在排队
    2. 设置与获取当前的线程(在父类AbstractOwnableSynchronizer中实现),
    3. CAS设置信号量(compareAndSetState,本人认为这里称为信号量更合适),获取信号量,

总结下来AQS给用户提供了CAS获取锁,修改信号量,对AQS内部感知,锁操作的方法

这些方法一次堆上来就会眼花缭乱,我们可以从ReentrantLock中获取如何使用这些方法。

ReentrantLock中如何使用AQS

ReentrantLock中分为公平和非公平锁,这里的公平意思是在获取锁的时候,非公平的锁会直接尝试进行获取,而公平的锁会先看看自己会不会排在第一个,这意味着一个线程释放锁后再次获取锁,成功的几率会较其他线程高些。

我们先看公平锁的实现,来理解如何使用AQS

image-20210222095912743

abstract static class Sync extends AbstractQueuedSynchronizer {
   
    private static final long serialVersionUID = -5179523762034025860L;
    
    final boolean tryLock() {
   
        Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
   
            if (compareAndSetState(0, 1)) {
   
                setExclusiveOwnerThread(current);
                return true;
            }
            //重入操作
        } else if (
  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值