精华内容
下载资源
问答
  • 原标题:Java 并发面试题:说下你对 AQS 的理解?Java 并发面试题:说下你对 AQS 的理解?这篇文章,我们来聊聊面试时一个比较有杀伤力的问题:聊聊你对AQS的理解?因为首先,很多人可能连AQS是什么都不知道。或者...

    原标题:Java 并发面试题:说下你对 AQS 的理解?

    Java 并发面试题:说下你对 AQS 的理解?

    这篇文章,我们来聊聊面试时一个比较有杀伤力的问题:聊聊你对AQS的理解?

    因为首先,很多人可能连AQS是什么都不知道。或者仅仅是听说过AQS这个名词,但是可能连全称怎么拼写都不知道。

    更有甚者,可能会说:AQS?是不是一种思想?我们平时开发怎么来用AQS?

    总结起来,很多同学都对AQS有一种云里雾里的感觉,如果用搜索引擎查一下AQS是什么,估计看几篇文章就直接放弃了,因为密密麻麻的文字,实在是看不懂!

    所以基于上述痛点,这篇文章就用最简单的大白话配合N多张手绘图,给大家讲清楚AQS到底是什么?

    让各位同学面试被问到这个问题时,不至于不知所措。

    二、ReentrantLock和AQS的关系

    首先来看看,如果用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的感觉?

    这个学过java的同学应该都会吧,毕竟是java并发基本API的使用,我们直接看一下代码:

    0db966b7b5152108294c47d192fa3f35.gif

    上面那段代码应该不难理解,无非就是搞一个Lock对象,然后加锁和释放锁。

    你这时可能会问,这个跟AQS有啥关系?

    关系大了去了!因为java并发包下很多API都是基于AQS来实现的加锁和释放锁等功能的,AQS是java并发包的基础类。

    举个例子,比如说ReentrantLock、ReentrantReadWriteLock底层都是基于AQS来实现的。

    那么AQS的全称是什么呢?

    AbstractQueuedSynchronizer,抽象队列同步器

    给大家画一个图,看一下ReentrantLock和AQS之间的关系。

    91e777f1982a17146f2ad00356cbbd23.gif

    我们看上图,说白了,ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。

    这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。

    三、ReentrantLock加锁和释放锁的底层原理

    好了,现在如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情?

    很简单,这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态。

    初始状态下,这个state的值是0。

    另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。

    ef3092141c92c9ed40cc16e5d3840349.gif

    接着线程跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。

    (关于CAS,之前专门有文章做过详细阐述,大家可以自行阅读了解)

    如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。

    一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。所以大家看下面的图,就是线程1跑过来加锁的一个过程。

    dc5b9816c142510b6ab8721007689e20.gif

    其实看到这儿,大家应该对所谓的AQS有感觉了。说白了,就是并发包里的一个核心组件,里面有state变量、加锁线程变量等核心的东西,维护了加锁状态。

    你会发现,ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的。

    这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁。

    可重入锁的意思,就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。

    大家看明白了那个state变量之后,就知道了如何进行可重入加锁!

    其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。

    接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?

    我们来看看锁的互斥是如何实现的?

    线程2跑过来一下看到,哎呀!state的值不是0啊?所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!

    接着线程2会看一下,是不是自己之前加的锁啊?当然不是了,“加锁线程”这个变量明确记录了是线程1占用了这个锁,所以线程2此时就是加锁失败。

    给大家来一张图,一起来感受一下这个过程:

    db8e1c4589188ccda77d9f61d90926c2.gif

    接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了

    所以大家可以看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!

    同样,给大家来一张图,一起感受一下:

    b47f480a27690d4bce643376cba0ec5a.gif

    接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁!他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null!

    整个过程,参见下图:

    接下来,会从等待队列的队头唤醒线程2重新尝试加锁。

    好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。

    此外,还要把“加锁线程”设置为线程2自己,同时线程2自己就从等待队列中出队了。

    最后再来一张图,大家来看看这个过程。

    f84c7d180e188a8520f9d3cb13bf743d.gif

    四、总结

    OK,本文到这里为止,基本借着ReentrantLock的加锁和释放锁的过程,给大家讲清楚了其底层依赖的AQS的核心原理。

    基本上大家把这篇文章看懂,以后再也不会担心面试的时候被问到:谈谈你对AQS的理解这种问题了。

    其实一句话总结:AQS就是一个并发包的基础组件,用来实现各种锁,各种同步组件的。

    它包含了state变量、加锁线程、等待队列等并发中的核心组件。返回搜狐,查看更多

    责任编辑:

    展开全文
  • 问题(1)AQS的定位?(2)AQS的重要组成部分?(3)AQS运用的设计模式?(4)AQS的总体流程?简介AQS的全称是AbstractQueuedSynchronizer,它的定位是为Java中几乎所有的锁和同步器提供一个基础框架。在之前的章节中,我们...

    问题

    (1)AQS的定位?

    (2)AQS的重要组成部分?

    (3)AQS运用的设计模式?

    (4)AQS的总体流程?

    简介

    AQS的全称是AbstractQueuedSynchronizer,它的定位是为Java中几乎所有的锁和同步器提供一个基础框架。

    在之前的章节中,我们一起学习了ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch的源码,今天我们一起来对AQS做个总结。

    状态变量state

    AQS中定义了一个状态变量state,它有以下两种使用方法:

    (1)互斥锁

    当AQS只实现为互斥锁的时候,每次只要原子更新state的值从0变为1成功了就获取了锁,可重入是通过不断把state原子更新加1实现的。

    (2)互斥锁 + 共享锁

    当AQS需要同时实现为互斥锁+共享锁的时候,低16位存储互斥锁的状态,高16位存储共享锁的状态,主要用于实现读写锁。

    互斥锁是一种独占锁,每次只允许一个线程独占,且当一个线程独占时,其它线程将无法再获取互斥锁及共享锁,但是它自己可以获取共享锁。

    共享锁同时允许多个线程占有,只要有一个线程占有了共享锁,所有线程(包括自己)都将无法再获取互斥锁,但是可以获取共享锁。

    AQS队列

    AQS中维护了一个队列,获取锁失败(非tryLock())的线程都将进入这个队列中排队,等待锁释放后唤醒下一个排队的线程(互斥锁模式下)。

    Condition队列

    AQS中还有另一个非常重要的内部类ConditionObject,它实现了Condition接口,主要用于实现条件锁。

    ConditionObject中也维护了一个队列,这个队列主要用于等待条件的成立,当条件成立时,其它线程将signal这个队列中的元素,将其移动到AQS的队列中,等待占有锁的线程释放锁后被唤醒。

    Condition典型的运用场景是在BlockingQueue中的实现,当队列为空时,获取元素的线程阻塞在notEmpty条件上,一旦队列中添加了一个元素,将通知notEmpty条件,将其队列中的元素移动到AQS队列中等待被唤醒。

    模板方法

    AQS这个抽象类把模板方法设计模式运用地炉火纯青,它里面定义了一系列的模板方法,比如下面这些:

    // 获取互斥锁

    public final void acquire(int arg) {

    // tryAcquire(arg)需要子类实现

    if (!tryAcquire(arg) &&

    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

    }

    // 获取互斥锁可中断

    public final void acquireInterruptibly(int arg)

    throws InterruptedException {

    if (Thread.interrupted())

    throw new InterruptedException();

    // tryAcquire(arg)需要子类实现

    if (!tryAcquire(arg))

    doAcquireInterruptibly(arg);

    }

    // 获取共享锁

    public final void acquireShared(int arg) {

    // tryAcquireShared(arg)需要子类实现

    if (tryAcquireShared(arg) < 0)

    doAcquireShared(arg);

    }

    // 获取共享锁可中断

    public final void acquireSharedInterruptibly(int arg)

    throws InterruptedException {

    if (Thread.interrupted())

    throw new InterruptedException();

    // tryAcquireShared(arg)需要子类实现

    if (tryAcquireShared(arg) < 0)

    doAcquireSharedInterruptibly(arg);

    }

    // 释放互斥锁

    public final boolean release(int arg) {

    // tryRelease(arg)需要子类实现

    if (tryRelease(arg)) {

    Node h = head;

    if (h != null && h.waitStatus != 0)

    unparkSuccessor(h);

    return true;

    }

    return false;

    }

    // 释放共享锁

    public final boolean releaseShared(int arg) {

    // tryReleaseShared(arg)需要子类实现

    if (tryReleaseShared(arg)) {

    doReleaseShared();

    return true;

    }

    return false;

    }

    获取锁、释放锁的这些方法基本上都穿插在ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch的源码解析中了,现在看他们是不是舒服多了,如果一开始就看这些源码,难免会很晕。

    需要子类实现的方法

    上面一起学习了AQS中几个重要的模板方法,下面我们再一起学习下几个需要子类实现的方法:

    // 互斥模式下使用:尝试获取锁

    protected boolean tryAcquire(int arg) {

    throw new UnsupportedOperationException();

    }

    // 互斥模式下使用:尝试释放锁

    protected boolean tryRelease(int arg) {

    throw new UnsupportedOperationException();

    }

    // 共享模式下使用:尝试获取锁

    protected int tryAcquireShared(int arg) {

    throw new UnsupportedOperationException();

    }

    // 共享模式下使用:尝试释放锁

    protected boolean tryReleaseShared(int arg) {

    throw new UnsupportedOperationException();

    }

    // 如果当前线程独占着锁,返回true

    protected boolean isHeldExclusively() {

    throw new UnsupportedOperationException();

    }

    这几个方法为什么不直接定义成抽象方法呢?

    因为子类只要实现这几个方法中的一部分就可以实现一个同步器了,所以不需要定义成抽象方法。

    总结

    今天我们大概讲了下AQS中几个重要的组成部分,搞明白了这几个结构,AQS对你将没有任何秘密可言,当然面试的时候能把这几个点答清楚,面试官也会眼前一亮的。

    (1)状态变量state;

    (2)AQS队列;

    (3)Condition队列;

    (4)模板方法;

    (5)需要子类实现的方法;

    彩蛋

    经过前面的学习,您能简要描述一下AQS获取互斥锁的大体流程吗?

    这里彤哥就不作答了,相信学习完前面的内容,答这道题不是个问题了,答不上来的还需要把下面的推荐阅读好好多看几遍^^

    推荐阅读

    欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

    展开全文
  • 程序员面试金典第6版offer企业114.3元(需用券)去购买 >啥?你连 AQS 是啥都不知道?如果想要精通 Java 并发的话, AQS 是一定要掌握的。今天跟着阿粉一起搞一搞基本概念AQS 是AbstractQueuedSynchronizer 的...

    程序员面试金典第6版offer企业题

    114.3元

    (需用券)

    去购买 >

    a1dd33141376b834bfaf4711730fbd98.png

    啥?你连 AQS 是啥都不知道?

    如果想要精通 Java 并发的话, AQS 是一定要掌握的。今天跟着阿粉一起搞一搞

    1f75ca7b478513e2fc886c61c194f39f.gif

    基本概念

    AQS 是AbstractQueuedSynchronizer 的简称,翻译成中文就是 抽象队列同步器 ,这三个单词分开来看:

    Abstract (抽象):也就是说, AQS 是一个抽象类,只实现一些主要的逻辑,有些方法推迟到子类实现

    Queued (队列):队列有啥特征呢?先进先出( FIFO )对吧?也就是说, AQS 是用先进先出队列来存储数据的

    Synchronizer (同步):即 AQS 实现同步功能

    以上概括一下, AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单而又高效地构造出同步器。

    AQS 内部实现

    AQS 队列在内部维护了一个 FIFO 的双向链表,如果对数据结构比较熟的话,应该很容易就能想到,在双向链表中,每个节点都有两个指针,分别指向直接前驱节点和直接后继节点。使用双向链表的优点之一,就是从任意一个节点开始都很容易访问它的前驱节点和后继节点。

    在 AQS 中,每个 Node 其实就是一个线程封装,当线程在竞争锁失败之后,会封装成 Node 加入到 AQS 队列中;获取锁的线程释放锁之后,会从队列中唤醒一个阻塞的 Node (也就是线程)

    AQS 使用 volatile 的变量 state 来作为资源的标识:

    private volatile int state;

    关于 state 状态的读取与修改,子类可以通过覆盖 getState() 和 setState() 方法来实现自己的逻辑,其中比较重要的是:

    // 传入期望值 expect ,想要修改的值 update ,然后通过 Unsafe 的 compareAndSwapInt() 即 CAS 操作来实现

    protected final boolean compareAndSetState(int expect, int update) {

    // See below for intrinsics setup to support this

    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

    }

    下面是 AQS 中两个重要的成员变量:

    private transient volatile Node head;   // 头结点

    private transient volatile Node tail;   // 尾节点

    关于 AQS 维护的双向链表,在源码中是这样解释的:

    The wait queue is a variant of a "CLH" (Craig, Landin, and Hagersten) lock queue.

    CLH locks are normally used for spinlocks.  We instead use them for blocking synchronizers,

    but use the same basic tactic of holding some of the control information

    about a thread in the predecessor of its node.

    也就是 AQS 的等待队列是 “CLH” 锁定队列的变体

    直接来一张图会更形象一些:

    c20634e7b365c9ebce97b8ef3b6ce3d7.png

    Node 节点维护的是线程,控制线程的一些操作,具体来看看是 Node 是怎么做的:

    static final class Node {

    /** Marker to indicate a node is waiting in shared mode */

    // 标记一个节点,在 共享模式 下等待

    static final Node SHARED = new Node();

    /** Marker to indicate a node is waiting in exclusive mode */

    // 标记一个节点,在 独占模式 下等待

    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */

    // waitStatus 的值,表示该节点从队列中取消

    static final int CANCELLED =  1;

    /** waitStatus value to indicate successor's thread needs unparking */

    // waitStatus 的值,表示后继节点在等待唤醒

    // 只有处于 signal 状态的节点,才能被唤醒

    static final int SIGNAL    = -1;

    /** waitStatus value to indicate thread is waiting on condition */

    // waitStatus 的值,表示该节点在等待一些条件

    static final int CONDITION = -2;

    /**

    * waitStatus value to indicate the next acquireShared should

    * unconditionally propagate

    */

    // waitStatus 的值,表示有资源可以使用,新 head 节点需要唤醒后继节点

    // 如果是在共享模式下,同步状态应该无条件传播下去

    static final int PROPAGATE = -3;

    // 节点状态,取值为 -3,-2,-1,0,1

    volatile int waitStatus;

    // 前驱节点

    volatile Node prev;

    // 后继节点

    volatile Node next;

    // 节点所对应的线程

    volatile Thread thread;

    // condition 队列中的后继节点

    Node nextWaiter;

    // 判断是否是共享模式

    final boolean isShared() {

    return nextWaiter == SHARED;

    }

    /**

    * 返回前驱节点

    */

    final Node predecessor() throws NullPointerException {

    Node p = prev;

    if (p == null)

    throw new NullPointerException();

    else

    return p;

    }

    Node() {    // Used to establish initial head or SHARED marker

    }

    /**

    * 将线程构造成一个 Node 节点,然后添加到 condition 队列中

    */

    Node(Thread thread, Node mode) {     // Used by addWaiter

    this.nextWaiter = mode;

    this.thread = thread;

    }

    /**

    * 等待队列用到的方法

    */

    Node(Thread thread, int waitStatus) { // Used by Condition

    this.waitStatus = waitStatus;

    this.thread = thread;

    }

    }

    AQS 如何获取资源

    在 AQS 中,获取资源的入口是 acquire(int arg) 方法,其中 arg 是获取资源的个数,来看下代码:

    public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

    }

    在获取资源时,会首先调用 tryAcquire 方法,这个方法是在子类中具体实现的

    如果通过 tryAcquire 获取资源失败,接下来会通过 addWaiter(Node.EXCLUSIVE) 方法,将这个线程插入到等待队列中,具体代码:

    private Node addWaiter(Node mode) {

    // 生成该线程所对应的 Node 节点

    Node node = new Node(Thread.currentThread(), mode);

    // 将 Node 插入到队列中

    Node pred = tail;

    if (pred != null) {

    node.prev = pred;

    // 使用 CAS 操作,如果成功就返回

    if (compareAndSetTail(pred, node)) {

    pred.next = node;

    return node;

    }

    }

    // 如果 pred == null 或者 CAS 操作失败,则调用 enq 方法再次自旋插入

    enq(node);

    return node;

    }

    // 自旋 CAS 插入等待队列

    private Node enq(final Node node) {

    for (;;) {

    Node t = tail;

    if (t == null) { // Must initialize

    if (compareAndSetHead(new Node()))

    tail = head;

    } else {

    node.prev = t;

    if (compareAndSetTail(t, node)) {

    t.next = node;

    return t;

    }

    }

    }

    }

    在上面能够看到使用的是 CAS 自旋插入,这是因为在 AQS 中会存在多个线程同时竞争资源的情况,进而一定会出现多个线程同时插入节点的操作,这里使用 CAS 自旋插入是为了保证操作的线程安全性

    现在呢,申请 acquire(int arg) 方法,然后通过调用 addWaiter 方法,将一个 Node 插入到了队列尾部。处于等待队列节点是从头结点开始一个一个的去获取资源,获取资源方式如下:

    final boolean acquireQueued(final Node node, int arg) {

    boolean failed = true;

    try {

    boolean interrupted = false;

    for (;;) {

    final Node p = node.predecessor();

    // 如果 Node 的前驱节点 p 是 head,说明 Node 是第二个节点,那么它就可以尝试获取资源

    if (p == head && tryAcquire(arg)) {

    // 如果资源获取成功,则将 head 指向自己

    setHead(node);

    p.next = null; // help GC

    failed = false;

    return interrupted;

    }

    // 节点进入等待队列后,调用 shouldParkAfterFailedAcquire 或者 parkAndCheckInterrupt 方法

    // 进入阻塞状态,即只有头结点的线程处于活跃状态

    if (shouldParkAfterFailedAcquire(p, node) &&

    parkAndCheckInterrupt())

    interrupted = true;

    }

    } finally {

    if (failed)

    cancelAcquire(node);

    }

    }

    在获取资源时,除了 acquire 之外,还有三个方法:

    acquireInterruptibly :申请可中断的资源(独占模式)

    acquireShared :申请共享模式的资源

    acquireSharedInterruptibly :申请可中断的资源(共享模式)

    到这里,关于 AQS 如何获取资源就说的差不多了,接下来看看 AQS 是如何释放资源的

    AQS 如何释放资源

    释放资源相对于获取资源来说,简单了很多。源码如下:

    public final boolean release(int arg) {

    // 如果释放锁成功

    if (tryRelease(arg)) {

    // 获取 AQS 队列中的头结点

    Node h = head;

    // 如果头结点不为空,且状态 != 0

    if (h != null && h.waitStatus != 0)

    // 调用 unparkSuccessor(h) 方法,唤醒后续节点

    unparkSuccessor(h);

    return true;

    }

    return false;

    }

    private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;

    // 如果状态是负数,尝试将它改为 0

    if (ws 

    compareAndSetWaitStatus(node, ws, 0);

    // 得到头结点的后继节点

    Node s = node.next;

    // 如果 waitStatus 大于 0 ,说明这个节点被取消

    if (s == null || s.waitStatus > 0) {

    s = null;

    // 那就从尾节点开始,找到距离 head 最近的一个 waitStatus<=0 的节点进行唤醒

    for (Node t = tail; t != null && t != node; t = t.prev)

    if (t.waitStatus <= 0)

    s = t;

    }

    // 如果后继节点不为空,则将其从阻塞状态变为非阻塞状态

    if (s != null)

    LockSupport.unpark(s.thread);

    }

    AQS 两种资源共享模式

    资源有两种共享模式:

    独占模式( Exclusive ):资源是独占的,也就是一次只能被一个线程占有,比如 ReentrantLock

    共享模式( Share ):同时可以被多个线程获取,具体的资源个数可以通过参数来确定,比如 Semaphore/CountDownLatch

    看到这里, AQS 你 get 了嘛?

    87061a461320e7931be09ca141b35780.png

    java 11官方入门(第8版)教材

    79.84元

    包邮

    (需用券)

    去购买 >

    f0f3f55624fb396b1764d42d6df88864.png

    展开全文
  • (这些重写方法很简单,无非是对于共享资源state的获取和释放)将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。这和我们以往通过实现接口的方式有很大区别,这是模板...

    同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):

    使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)

    将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

    这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。

    模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票buyTicket()->安检securityCheck()->乘坐某某工具回家ride()->到达目的地arrive()。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了ride()方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 ride()方法。

    AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:

    `java

    isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。

    tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。

    tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。

    tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

    tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

    `

    默认情况下,每个方法都抛出 UnsupportedOperationException。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。

    以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

    再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

    一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

    展开全文
  • 面试题——AQS

    2019-09-04 12:23:55
    AQS 抽象同步队列简称AQS,它是一个先进先出的双向队列。AQS内部有一个CLH队列,用来记录所有等待锁的线程。AQS还拥有一个内部类ConditionObject,它是条件变量,每个条件变量的内部都维护了一个条件队列,当一个...
  • 原标题:Java 并发面试题:说下你对 AQS 的理解?Java 并发面试题:说下你对 AQS 的理解?这篇文章,我们来聊聊面试时一个比较有杀伤力的问题:聊聊你对AQS的理解?因为首先,很多人可能连AQS是什么都不知道。或者...
  • 十六、谈谈AQS AQS全称AbstractQueuedSynchronizer,是java并发包中的核心类,诸如ReentrantLock,CountDownLatch等工具内部都使用了AQS去维护锁的获取与释放。 AQS内部结构 类似一个阻塞队列,当前持有锁的线程...
  • 问题(1)AQS的定位?(2)AQS的重要组成部分?(3)AQS运用的设计模式?(4)AQS的总体流程?简介AQS的全称是AbstractQueuedSynchronizer,它的定位是为Java中几乎所有的锁和同步器提供一个基础框架。在之前的章节中,我们...
  • "Semaphore(信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问 某个资源,Semaphore(信号量)可以指定多个线程...开课吧广场Java面试 CyclicBarrier(循环栅栏): CyclicBarrier
  • Java 并发高频面试题:聊聊你对 AQS 的理解?

    千次阅读 多人点赞 2021-03-30 23:50:22
    AQS有点难,但是学会了很多类你都懂了
  • 面试题:了解Java的AQS吗?

    千次阅读 热门讨论 2019-06-15 09:08:36
    AQS是用于实现独占锁或共享锁的,对于一个锁来说,最重要的就是lock和unlock操作了,对应到AQS中,为acquire、release方法,由于AQS需要和子类进行“ 合作 ”, lock方法调用内部类的acquire方法,也就是AQS的...
  • 这篇文章,我们来聊聊面试时一个比较有杀伤力的问题:聊聊你对AQS的理解? 因为首先,很多人可能连AQS是什么都不知道。或者仅仅是听说过AQS这个名词,但是可能连全称怎么拼写都不知道。 更有甚者,可能会说:AQS?...
  • 不依赖看帖子、博客和百度和面试题 0、大厂面试复盘 京东一面:AQS源码 阿里一面:syn lock redis新的数据结构 mysql 调优 快手一面:java mysql spring循环依赖重点,三级缓存,有没有准备? 58同城: java基础,...
  • 十六、谈谈AQS AQS全称AbstractQueuedSynchronizer,是java并发包中的核心类,诸如ReentrantLock,CountDownLatch等工具内部都使用了AQS去维护锁的获取与释放。 AQS内部结构 类似一个阻塞队列,当前持有锁的线程...
  • 暖身面试题 Redis默认端口是多少?- 6379 link Spring官网地址 - https://spring.io 经典计算机图书看过吗? 01_字符串常量Java内部加载-上 Returns a canonical representation for the string object. A pool ...
  • 面试官肯定没少问你吧) 【回顾】jvm中的堆栈与数据结构中的堆栈 堆栈这个概念存在于数据机构中,也存在于jvm虚拟机中,但是这两个概念不是相同的。栈(局部变量),堆(对象) 在数据结构中,堆和栈是数据结构,...
  • 可tryLock与lockInterruptibly 非公平锁 公平锁、非公平锁 AQS:Abstract Queue Synchronizer 抽象队列同步 核心变量 state(volatile的):尝试用CAS方式去更新state=1 等待队列:线程2加锁失败,进入等待队列,挂起...
  • Semaphore(信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问 某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。... 免费领取Java面试题(大厂面试必备)
  • 补充几道面试题 锁升级过程:无锁、偏向锁、轻量级锁、重量级锁 StampedLock 自己看一下 面试题:syn和Reentrantlock的区别? LockSupport LockSupport.park()当前线程停止 t.start() LockSupport.unpark(t);让...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 275
精华内容 110
关键字:

aqs面试题