深入理解AQS(AbstractQueueSynchronizer)

目录

1.引入

什么是AQS

2.AQS的架构设计

AQS可以做什么

AQS中主要包含什么

AQS的工作原理

Condition的工作原理

3.AQS的应用

使用AQS的好处

自己实现AQS


1.引入

 

什么是AQS

说到AQS(AbstractQueueSynchronizer),很多同学都表示没听过。但是重入锁,读写锁这些同步工具大家肯定都用过,AQS都是这些锁底层实现同步不可或缺的一个组件。

下面就来聊聊AQS


2.AQS的架构设计

 

AQS可以做什么

首先AQS是个抽象类,本身不能实例化,需要使用者根据实际情况去继承它。

AQS的主要功能是提供线程同步,通俗来说就是可以控制多个线程,让它们阻塞或者唤醒。通过AQS,我们能够实现像重入锁那样单个线程访问临界区代码,让其他线程等待;也可以实现像读写锁那样控制多个读线程同时访问资源。

AQS中主要包含什么

AQS中首先有一个Node内部类,用来封装线程,并且通过一个head节点和tail节点来形成Node链表,实现同步线程队列。还有一个整型变量state,用来表示锁的状态(这里说的不单单是锁,也可以用来表示其他其他互斥资源),进入AQS通过判断state的值来进行相应的操作(获取锁或者进入队列等待)。还有多个关键方法(acquire等),这些方法暴露出去,实现加锁,解锁的功能。还有一个ConditionObject(下文用Condition代替)对象,这个对象是用来实现类似wait/nodify机制的。但是通过AQS设计的锁更加高端,可以创建多个Condition对象,实现多组等待唤醒控制,而传统的synchronized只能通过wait/notify方法实现一组。

AQS的工作原理

AQS主要实现的功能是让一个或多个线程能正确获取锁(或资源)。例如同一时间只有一个线程获得锁,其他线程都等待该线程释放锁。

如上图所示,AQS用一个链表存储要获取锁的线程。每次外来线程想要获取锁时,都通过线程控制器进入同步线程队列。这个线程控制器是我抽象出来的,如果查看源码的话就能发现进入AQS的线程其实是进入了一个自旋状态,每次自旋通过CAS操作去修改state的值,如果修改成功就获得了锁。

比方说state的值为0时表示锁可用,那么CAS操作把他置为1,然后获得锁。这保证同一时间只有指定数量的线程能获取到锁。

后续进来的线程在自旋中并不能通过CAS操作把state的值从0变为1,那么就说明现在锁已被占用,那么这个线程就在同步线程队列中阻塞等待。

一般来说能拿到锁的都是头节点中的线程(非Head节点的第一个节点,接下来的头节点指的都是它)。那么当头节点线程释放锁时会唤醒它的后继节点,然后后继节点又进行一次自旋, 看看自己是不是头节点的next的节点,能不能通过CAS操作修改state的值,如果能就把头节点出队,自己成为头节点。

Condition的工作原理

使用wait/notify方法的前提是获得锁,使用Condition对象也采用了一样的设计。获得锁时,调用await方法能让当前线程释放锁,进入等待状态,其他获取锁的线程调用signal方法能唤醒调用同一Condition对象await方法进行等待的线程。

这整个过程其实AQS的工作原理很像,Condition内部也有一个链表实现的等待队列,如果某个获取锁的线程调用await的方法,就把这个线程从AQS的同步线程队列中出队,包装放入Condition对象的等待队列。然后其他获得锁的线程调用该Condition对象的signal方法时就把等待队列头节点出队,放到AQS同步线程队列的队尾去等待获取锁。

Condition和AQS操作链表的方式不同的地方在于Condition操作自己的等待队列时不用CAS的操作,因为操作等待队列的线程都是获取到了锁的线程,因此不存在竞争。但是AQS就不同了,比方说同时有多个线程要进入同步线程队列,那么如果不用CAS操作就会发生混乱。


3.AQS的应用

 

使用AQS的好处

在实际开发中,我们很少会直接去使用AQS,但是却很常用重入锁和读写锁,尤其是重入锁,在某些情况下会比传统的synchronized效率更高,这就得益于它内部的AQS,AQS用自旋CAS的方式减少了线程阻塞带来的开销。

而且重入锁还支持有限等待,公平获取锁,多条件等待唤醒等等的功能,这是synchronized所不具备的。

自己实现AQS

有些时候还真得自己实现一个AQS来实现一些功能。虽然AQS是个抽象类,但他没有任何抽象方法。我们使用它只需要通过继承覆写方法实现我们具体的需求即可。

下面结合重入锁对AQS的实现来聊聊怎么实现AQS。

操作同步线程队列的逻辑AQS都已经写好了,在这一块不需要做修改。主要重写的部分是实现资源获取逻辑。

还是用重入锁举例子,重入锁的功能是保证同一时刻只有一个线程可以访问临界区代码,也就是只有一个线程能获得锁,来看看它获取锁的代码。

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //调用了AQS的acquire方法,传入1作为参数
                acquire(1);
}
public final void acquire(int arg) {
        //tryAcquire尝试去修改state状态,如果成功就继续执行代码。
        if (!tryAcquire(arg) &&
            //不成功就在acquireQueued方法中进入同步线程队列
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
            //尝试以不公平的方式获取锁
            return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
            //获得当前线程
            final Thread current = Thread.currentThread();
            int c = getState();//获得state状态
            if (c == 0) {
                //如果state的值为0,表示锁可用,这时用CAS操作把他修改1,如果成功就成功获得锁。
                if (compareAndSetState(0, acquires)) {
                    //记录获得锁的线程,为以后该线程重新获取锁做准备
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果state的值不是0,说明有其他线程已经获取锁,这是查看一下获得锁的线程是不是本线程
            else if (current == getExclusiveOwnerThread()) {
                //如果是就修改state的值
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //没有获得锁,返回false
            return false;
}

重入锁主要是重写了tryAcquire方法(还有对应的释放锁方法tryRelease),这两个个方法在AQS中是直接抛出UnSupportException异常的。这个方法就是操作锁的逻辑,如果我们要根据自己的业务需求自己实现AQS,首选的就是重写这一套方法。

AQS还有一套操作锁的方法tryAcquireShared和tryReleaseShared,这个主要是用来实现多线程同时获取锁的。比如读写锁中的读锁就是用这一套方法设计,读锁允许同时多个读线程获得锁。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值