每日一面——谈谈你对AQS的理解

菜鸡每日一面系列打卡21

每天一道面试题目 

助力小伙伴轻松拿offer

坚持就是胜利,我们一起努力!

题目描述

谈谈你对AQS的理解。

题目分析

在之前的文章中我们提到过,ReentrantLock等一系列JDK所提供的同步器都是基于AQS构建的,因此可以说,不了解AQS,就不算真正掌握了同步器。而有关AQS的底层原理的考查,也是各个大厂面试过程中的热点问题。

本文将从源码级别深入分析AQS,并以ReentrantLock为例,介绍如何使用AQS构建同步器。不了解ReentrantLock的小伙伴,可以先移步文末的相关链接进行学习。菜鸡希望,本篇文章能使小伙伴们彻底搞懂AQS以及模板方法设计模式,从而可以随心所欲地构建自定义同步器,那么,面试自然就不在话下了。

接下来,随菜鸡一起去看看吧。

题目解答

01

什么是AQS

AQSAbstractQueuedSynchronizer类的简称,从名字我们可以猜测,AQS是一个虚拟类,基于队列实现,用于构建同步器。这个猜测是正确的,它是JDK提供的一个基于FIFO等待队列实现阻塞锁及相关同步器(Semaphore等)的一个框架,它是大多数同步器的基础,用一个volatileint类型变量state来表示状态。


AQS的核心设计思想是,如果当前线程请求的共享资源处于闲置状态,则将该线程设置为工作线程,并将该资源设置为锁定状态。如果该资源处于锁定状态,则将该线程加入CLH队列中。

那么问题来了!什么是CLH队列?无需解释,看完下图就全明白了。

02

源码分析

上文谈到了AQS是用一个volatileint类型变量state来表示状态。接下来我们看一下源码。

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


    ......


    private volatile int state;


    protected final int getState() {
        return state;
    }


    protected final void setState(int newState) {
        state = newState;
    }
    
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }


    ......


}

AQS定义了两种资源共享方式,独占锁与共享锁。所谓的独占锁就是,只有一个线程可以执行,比如之前谈过的ReentrantLock就是独占锁的一种,而共享锁,就是多个线程可以执行,比如上文提到的Semaphore。在此就不展开。

ReentrantLock为例,我们可以学习基于AQS的公平锁与非公平锁的实现。我们看一下ReentrantLock类中相关部分的源码,首先,我们看一下它的构造方法。

public class ReentrantLock implements Lock, java.io.Serializable {
    
    ......
    
    private final Sync sync;
    
    ......
    
    public ReentrantLock() {
        sync = new NonfairSync();
    }


    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    ......
    
}

如源码所示,ReentrantLock的默认构造函数是非公平锁。接下来,我们看一下NonfairSync类与FairSync类的不同之处。首先,我们看一下公平锁的实现。

public class ReentrantLock implements Lock, java.io.Serializable {


    ......
    
    // 非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }


        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }


    // 公平锁
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;


        final void lock() {
            acquire(1);
        }


        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    
}

可以看出,非公平锁和公平锁主要有以下不同:

  • 二者的lock方法不同,非公平锁在调用lock方法后,会CAS抢占锁,如果锁没有被占用,则直接获取。

  • 非公平锁在CAS失败后,会同公平锁一样进入到tryAcquire方法,如果发现锁被释放(state == 0),非公平锁会CAS抢占锁,而公平锁则会检查等待队列是否有处于等待状态的线程。

03

模板方法模式

同步器的设计是基于模板方法模式的,从ReentrantLock的源码我们也可以看出,自定义同步器方式大致如下:

  • 使用者继承AQS并重写指定的方法。

  • AQS以组合的形式引入自定义同步组件的实现中,并调用其模板方法,模板方法会调用重写的方法。

  • 具体需要重写的方法可以参考ReentrantLock的源码,在此便不再赘述。

以上便是菜鸡对AQS的一些总结,供大家参考。

相关链接

每日一面——谈谈你对ReentrantLock的理解

学习 | 工作 | 分享

????长按关注“有理想的菜鸡

只有你想不到,没有你学不到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章