《Java并发编程的艺术》4.AQS上

》》AQS

参考路飞大佬AQS

1.1概述

队列同步器AbstactQueuedSynchronizer(以下简称同步器,是一个抽象类),是用来构建锁或者其他同步组件的基础框架,它使用了一个volatile修饰的int成员变量表示同步状态(volatile int state),通过内置的FIFO队列来通过CAS完成资源获取线程的排队工作。 并发包的作者Doug lea期望它能够成为大部分同步需求的基础。

在这里插入图片描述

同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对它的抽象方法来管理同步状态,这时候就需要使用同步器提供的三个方法(getState(), setState(int newState)compareAndSetState(int expect, int update)),因为它们能够保证状态的改变是安全的,

子类被推荐被定义为自定义同步组件的静态内部类,同步器本身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式的获取同步状态,也可以支持共享式地获取同步状态。这样就可以方便实现不同类型的同步组件(ReentrantLock, CountDownLatch等)。

1.2AQS的接口与实例

同步器的设计是基于模板方法设计模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义的同步组件的实例中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

重写同步器指定的方法时,需要使用同步器提供的如下3个方法来**访问或修改同步状态。**这三个方法都是在AQS中使用final修饰符定义的方法,即不允许子类重写。

protected final int getState() {
    
     //获取当前同步状态
        return state;
}

protected final void setState(int newState) {
    
     //设置当前同步状态
        state = newState;
}

//使用CAS设置当前状态,该方法能够保证状态设置的原子性
protected final boolean compareAndSetState(int expect, int update) {
    
    
    	//使用unsafe类的CAS操作
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

同步器可以重写的方法与描述如下

让子类重写的方法。

//独占式获取同步状态, 实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS操作设置同步状态
protected boolean tryAcquire(int arg);


//独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
protected boolean tryRelease(int arg) 
    
//共享式获取同步状态,返回大于等于0的值,表示获取成功,反之获取失败
protected int tryAcquireShared(int arg)   

//共享式释放同步状态
protected boolean tryReleaseShared(int arg)    
    
//当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占。    
protected boolean isHeldExclusively()    
    

实现自定义同步组件时,就会调用同步器提供的模板方法,即都是AQS类中的重要且不允许子类重写(都定义为final的方法)方法。

//独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法。
public final void acquire(int arg) {
    
    
       xxx...
}

//独占式的释放同步状态,该方法会在释放同步状态后,将同步队列中第一个节点包含的线程唤醒。
public final boolean release(int arg) {
    
    
	   xxx...
}

.....

同步器提供的模板方法,基本上分为3类:独占式获取与释放同步状态共享式获取与释放同步状态查询同步队列中的等待线程情况

顾名思义,独占锁就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁。

1.3AQS属性分析

1.3.1Node

队列中的节点Node,是AQS中的一个内部类,整体是一个双向链表构成的队列

    static final class Node {
    
    
        //标识一个节点是共享模式
        static final Node SHARED = new Node();
        
        //标识一个节点是互斥模式
        static final Node EXCLUSIVE = null;
		
        //标识线程已取消 (标识当前节点处于取消状态)
        static final int CANCELLED =  1;
        //表示后继节点需要唤醒 (表示当前节点需要唤醒它的后继节点)
        static final int SIGNAL    = -1;
        
        //表示线程等待在一个条件上
        static final int CONDITION = -2;
		
        //标识后面的共享锁需要无条件的传播(共享锁需要连续唤醒读的锁)
        static final int PROPAGATE = -3;
		
// 当前节点保存的线程对应的等待状态(node状态可选值:0,SIGNAL,CANCELLED,CONDITION,PROPAGATE)
    // waitStatus == 0 默认状态
    // waitStatus > 0 取消状态
    // waitStatus == -1 表示当前node如果是head节点时,释放锁之后需要唤醒它的后继节点
        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() {
    
        
        }

        Node(Thread thread, Node mode) {
    
         
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) {
    
     
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

1.3.2主要属性

注意:这几个变量都要使用volatile关键字来修饰,因为是在多线程环境下操作,要保证它们的值修改之后对其它线程立即可见。

// 队列的头节点: 任何时刻,头结点对应的线程就是当前持锁线程~
private transient volatile Node head;

// 队列的尾节点:(阻塞队列不包含头结点head,是从head.next 开始,到 tail 结束,这个区间是阻塞队列~ 因为头结点是持锁线程,没有被阻塞)
private transient volatile Node tail;

// 控制加锁解锁的状态变量
// 独占模式下:0 表示未加锁状态, >0 表示已加锁状态
private volatile int state;

这几个变量的修改是直接使用Unsafe这个类来操作的。

// 获取Unsafe类的实例,注意这种方式仅限于jdk自己使用,普通用户是无法这样调用的
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 状态变量state的偏移量
private static final long stateOffset;
// 头节点的偏移量
private static final long headOffset;
// 尾节点的偏移量
private static final long tailOffset;
// 等待状态的偏移量(Node的属性)
private static final long waitStatusOffset;
// 下一个节点的偏移量(Node的属性)
private static final long nextOffset;

static {
    
    
    try {
    
    
        // 获取state的偏移量
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        // 获取head的偏移量
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
        // 获取tail的偏移量
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        // 获取waitStatus的偏移量
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
        // 获取next的偏移量
        nextOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("next"));

    } catch (Exception ex) {
    
     throw new Error(ex); }
}

// 调用Unsafe的方法原子CAS更新state
protected final boolean compareAndSetState(int expect, int update) {
    
    
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

1.3.3父类属性

AQS还用到了其父类AbstractOwnableSynchronizer的一些属性:

// 独占模式下:表示当前持有锁的线程~
private transient Thread exclusiveOwnerThread;

1.4加锁资源竞争逻辑

以ReentrantLock的lock为例

1.首先调用ReentrantLock类内部的静态类Sync(继承与AQS)的加锁方法

final void lock() {
    
    
    // 令当前线程去竞争资源
    acquire(1);
}

2.调用了AQS中的acquire()去获取锁

public final void acquire(int arg) {
    
    
    /*
	 *  1.tryAcquire()也是ReentrantLock内部的方法,就是让子类重写的方法。 此方法尝试获取锁,获取成功返回true 失败返回false 	    
     *  2.1 addWaiter() 将当前线程封装成一个Node对象入队
     *  2.2入队后调用acquireQueued方法(该方法包含挂起当前线程,以及线程唤醒后的相关的逻辑) -> 令当前线程不断去竞争资源,直到成功获取锁才停止自旋
     *  
     *  acquireQueued()方法返回boolean类型,true表示挂起过程中线程中断唤醒过,
     *  false表示未被中断唤醒过
     *  
     *  Node.EXCLUSIVE表示当前节点是独占模式
     */
     
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
            selfInterrupt(); //这里表示没有获取锁 但是当前线程被中断了 就执行中断over
    }

2.1获取锁的具体逻辑还是子类实现的,位于ReentrantLock类的静态内部类Sync中

公平锁逻辑

// 返回true -> 尝试获取锁成功 | 返回false -> 尝试获取锁失败
protected final boolean tryAcquire(int acquires) {
    
    
    // 获取当前线程。
    final Thread current = Thread.currentThread();
    
    // 获取state的当前值
    int c = getState();
    
    // 如果条件成立:c == 0 表示当前AQS处于无锁状态
    if (c == 0) {
    
    
        // 因为fairSync是公平锁,任何时候都需要检查一下在当前线程之前,队列中是否有等待者
        // 条件1:hasQueuedPredecessors 判断FIFO队列是否为空 
        // true -> 表示当前线程前面有等待者线程,当前线程需要入队等待
        // false -> 表示当前线程前面没有等待者线程,直接可以尝试获取锁
        if (!hasQueuedPredecessors() &&
            // 条件2:compareAndSetState(0, acquires) 基于CAS去更新state的值
            // state更新成功:说明当前线程抢占锁成功!
            // state更新失败:说明多个线程存在竞争,当前线程竞争失败,未能抢到锁的持有权
            compareAndSetState(0, acquires)) {
    
    
            // 条件1、2均成立时:说明当前线程抢夺锁的持有权成功!
            // 设置当前线程为独占线程(锁的持有者线程)
            setExclusiveOwnerThread(current);
            // true -> 当前线程尝试获取锁成功
            return true;
        }
    }
    //重入锁的逻辑
    // current == getExclusiveOwnerThread():用于判断当current !=0 或者 >0 的情况下
    // 当前线程是否是持有锁的线程(独占线程),因为ReentrantLock是可重入的锁,获取锁的线程可以再次进入~
    // 如果条件成立:说明当前线程就是独占锁的线程
    else if (current == getExclusiveOwnerThread()) {
    
    
        // 获取当前线程的加锁状态,并累加
        int nextc = c + acquires;
        // 越界判断...当冲入的深度很深时,会导致 nextc < 0,因为 int值达到MAX最大之后,再+1,会变复数
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 更新加锁状态
        setState(nextc);
        // true -> 当前线程尝试获取锁成功
        return true;
    }
    // false -> 尝试获取锁失败的情况:
    // 1.CAS加锁失败 且 当前线程前面有等待的线程
    // 2.state > 0 且 当前线程不是占用锁的线程
    return false;
}

非公平锁逻辑

final boolean nonfairTryAcquire(int acquires) {
    
    
    final Thread current = Thread.currentThread(); //拿到当前线程
    int c = getState(); //获得state
    //加锁的逻辑
    if (c == 0) {
    
     //没有线程占用锁
        if (compareAndSetState(0, acquires)) {
    
     //CAS操作 更新state
            setExclusiveOwnerThread(current); //设为当前线程独占锁
            return true;
        }
    }
    //重入锁的逻辑
    else if (current == getExclusiveOwnerThread()) {
    
     //本线程已经占有锁了 
        int nextc = c + acquires;  
        if (nextc < 0) // overflow 因为爆int的话就会变为负数,这里要判断。
            throw new Error("Maximum lock count exceeded");
        setState(nextc); //state + 1
        return true;
    }
    return false;
}

3.AQS中将当前线程封装成一个Node添加到阻塞队列的方法

// 位于AQS下:将当前线程添加到阻塞队列的方法
// 最终返回包装当前线程的node
private Node addWaiter(Node mode) {
    
    
    // 构建Node,把当前线程封装到Node中,mode:Node节点的模式,例如Node.EXCLUSIVE 当前节点是独占模式
    Node node = new Node(Thread.currentThread(), mode);
    
    // 获取队尾节点
    Node pred = tail;
    if (pred != null) {
    
    // 如果条件成立:说明队列中已经有node了
        // 令当前节点node的前驱等于pred
        node.prev = pred;
        // 基于CAS更新队尾tail
        if (compareAndSetTail(pred, node)) {
    
    
            // tail更新成功:前驱节点等于node,完成双向绑定
            pred.next = node;
            // 返回node
            return node;
        }
    }
    // 线程完整入队方式(自旋入队):
    // 执行到这里有以下2种情况:
    // 1.tail == null 当前队列是空队列
    // 2.cas设置当前newNode 为 tail 时失败了,被其他线程抢先一步了
    // 自旋入队,只有入队成功才结束自旋:
    enq(node);
    
    // 返回node
    return node;
}

如果上一步CAS入队没有成功,即被其他线程抢先一步,那么将会进入AQS的enq方法无限自旋入队。

private Node enq(final Node node) {
    
    
    // 自旋~ 只有封装当前线程的node入队成功,才会跳出循环
    for (;;) {
    
    
        Node t = tail;
        // 第1种情况:空队列 ===> 即,当前线程是第一个抢占锁失败的线程
        // 当前持有锁的线程(注:tryAcquire方法直接获取到锁的线程,在该方法逻辑中,并没有将持锁线程入队,
        // 而按理说阻塞队列的head节点就应该是当前持有锁的线程才对)并没有设置过任何 node,
        // 所以作为该线程的第一个后驱next,需要给它擦屁股(给持锁线程补一个node节点并设置为阻塞队列的head
        // head节点任何时候,都代表当前占用锁的线程)
        if (t == null) {
    
    
            // 如果compareAndSetHead条件成立:说明当前线程给当前持有锁的线程,补充head操作成功了!
            if (compareAndSetHead(new Node())) //补充一个head
                // tail = head 表示当前队列只有一个元素,这里就表名当前持锁的线程被放入阻塞队列且为head了~
                tail = head;
            	// 注意:并没有直接返回,还会继续自旋,下次再进入循环时阻塞队列已经不为空,且head为持锁线程节点了...
        } else {
    
    
            // 其他情况,说明:当前队列中已经有node了,这里是一个追加node的过程

            // 如何入队呢?和 addWaiter方法入队逻辑一样~
            // 1.找到newNode的前置节点 pred
            // 2.更新newNode.prev = pred
            // 3.CAS更新tail为 newNode
            // 4.更新 pred.next = newNode

            // 前置条件:队列已经有等待者node了(不为空),当前node并不是第一个入队的node
            node.prev = t;
            if (compareAndSetTail(t, node)) {
    
    
                // 如果条件成立,说明当前线程成功入队!
                t.next = node;
                // 注意:入队成功,一定要return终止无限for循环~
                // 返回这个节点t
                return t;
            }
        }
    }
}

AQS中入队后真正抢占锁的逻辑

final boolean acquireQueued(final Node node, int arg) {
    
    
        boolean failed = true;
        try {
    
    
            boolean interrupted = false;
            //死循环(自旋) 直到获取锁成功。
            for (;;) {
    
    
                //获取前驱节点
                final Node p = node.predecessor(); 
                //如果前驱节点是头结点并且当前节点获得锁成功
                if (p == head && tryAcquire(arg)) {
    
     
                    setHead(node); //将当前节点设为头结点
                    p.next = null; // help GC
                    failed = false; 
                    return interrupted;
                }
                
                //获取锁失败
                /*
			     * shouldParkAfterFailedAcquire()当前线程获取锁失败后,是否需要挂起
                 * true需要挂起, false不需要挂起
                 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    /*
                     *  parkAndCheckInterrupt()  挂起当前线程,并在该线程唤醒之后,返回当前线程的interrupted中断标记
                     *  唤醒该线程的方式: 
                     *  1、正常唤醒 其他线程调用unpark方法,唤醒该线程
                     *  2、其他线程给当前挂起的线程一个中断信号(中断挂起)
                     */
                    parkAndCheckInterrupt())
                    //表示当前node对应的线程是被中断信号唤醒的。
                    interrupted = true;
            }
        } finally {
    
    
            if (failed)
                cancelAcquire(node); //取消线程
        }
    }

AQS中当前入队的线程获取锁失败后,是否需要挂起

// 位于AQS中: 判断当前线程获取锁资源失败后,是否需要挂起
// true: 需要挂起 | false:不需要挂起
// 参数1:Node pred 当前线程的前驱节点
// 参数2:Node node 封装当前线程的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
    // 获取前驱节点的状态waitStatus
    // 0: 默认状态 | -1:Signal状态(表示当前节点释放锁后会唤醒它的第一个后驱节点) |
    // >0:表示当前节点是CANCELED状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)// 如果条件成立,则表示前驱节点是可以唤醒当前线程节点的节点
        // 返回true后,在acquireQueue方法中会继续调用parkAndCheckInterrupt方法去park当前线程节点
        // 注意:一般情况下,第一次来到shouldParkAfterFailedAcquire方法中时,ws不会是-1
        return true;
    // 如果ws>0条件成立:表示当前节点是CANCELED状态
    if (ws > 0) {
    
    
        // 该循环是一个找pred.waitStatus > 0 的前驱节点的过程:
        do {
    
    
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        // 找到符合条件的前驱节点后,令其下一个节点为当前线程的node
        // 隐含着一种操作:即,CANCELED状态的节点会被出队 
        pred.next = node;
    } else {
    
    
        // 当前node前驱节点的状态就是0,即默认状态这种情况
        // 将当前线程node的前驱节点的状态,强制设置为SIGNAL,表示该节点释放锁后会唤醒它的第一个后驱节点
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

// 位于AQS中:挂起当前线程节点
private final boolean parkAndCheckInterrupt() {
    
    
    // 线程挂起
    LockSupport.park(this);
    return Thread.interrupted();
}

1.5解锁逻辑(简单介绍)

AQS中的release()

该方法执行时,会唤醒头结点的后继节点线程,unparkSuccessor(Node node)方法使用LockSupport来唤醒等待状态的线程。

    public final boolean release(int arg) {
    
    
        //tryRelease()尝试释放锁
        // true 当前线程已经完全释放锁
        // false 当前线程未完全释放锁
        if (tryRelease(arg)) {
    
    
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //唤醒后继节点。
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

1.6加锁解锁逻辑总结

流程图

在这里插入图片描述

总结

同步器(AQS)依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时(非公平锁的情况,公平锁上来直接就去队列,不会直接获取锁),同步器会将当前线程以及等待状态等信息构造称为一个节点(Node)并使用CAS的方式将其加入同步队列的末尾。

当一个线程成功地获取到了同步状态,其他线程将会被封装成Node并加入同步队列,而这个加入队列的过程必须要保证线程安全,而加锁会导致锁定整个队列,所以为了提高效率,AQS提供了一个基于CAS的设置尾结点的方法:compareAndSetTail(Node expect, Node update),需要传递当前线程认为的尾结点和的当前节点,只有设置成功后,当前节点才正式与之前的尾结点建立关联。

只有头结点是成功获取同步状态成功的节点,头结点的线程在释放同步状态时,将会唤醒它的后继节点,而后继节点将会在获取同步状态成功时将自己设置为头结点。

设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头结点的方法并不需要使用CAS来保证。它只需要将首节点设置成为原首节点的后继节点并断开原头结点的next引用即可。

整个加锁逻辑:首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法保证线程安全(CAS)的获取同步状态,如果同步状态获取失败,则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态),并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部,最后调用acquireQueued(Node node, int arg)方法,使得该节点以死循环的方式获取同步状态,如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。

分析了独占式同步状态和释放过程后,适当做个总结: 在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移除队列(或停止自旋)的条件是前驱结点为头结点成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头结点的后继节点.

1.7共享式同步状态获取与释放

共享式获取与独占式获取的最主要区别在于同一时刻能否有多个线程同时获取到同步状态。 以文件的读写为例,如果一个程序对文件进行读操作,那么这一时刻对于该文件的写操作均被阻塞,而读操作能够同时进行。写操作要求对资源的独占式访问,而读操作可以是共享式访问,两种不同的访问模式在同一时刻对文件或资源的访问情况,如下图所示。

在这里插入图片描述

左半部分,共享式访问资源时,其他共享式的访问均被允许,而独占式访问被阻塞,

右半部分是独占式访问资源时,同一时刻其他访问均被拒绝。


通过调用AQS的acquireShared(int arg)方法可以共享式的获取同步状态。

    private void doAcquireShared(int arg) {
    
    
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
    
    
            boolean interrupted = false;
            for (;;) {
    
    
                final Node p = node.predecessor(); //获取前驱节点
                if (p == head) {
    
     //前驱节点是头结点
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
    
     //返回值大于0 表示获取同步状态成功
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //挂起逻辑
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
    
    
            if (failed)
                cancelAcquire(node);
        }
    }

在acquireShared(int arg)方法中,同步器调用**tryAcquireShared(int arg)**方法尝试获取同步状态,tryAcquireShared(int arg)方法返回值为int类型,当返回值大于等于0时,表示能够获取到同步状态,因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。可以看到,在doAcquireShared(int arg)方法的自旋过程中,如果当前节点的前驱是头节点,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出

1.8独占式超时获取同步状态

通过调用AQS的doAcquireNanos(int arg,long nanosTimeout)方法可以超时获取同步状态,即在指定的时间段内获取同步状态,如果获取到同步状态则返回true,否则,返回false该方法提供了传统的Java同步操作(Synchronized)关键字所不具备的特性。

在分析该方法的实现前,先介绍一下响应中断的同步状态获取过程。在Java5之前,当一个线程获取不到锁而被阻塞在synchronized之外时,对该线程进行中断操作,此时该线程的中断标志位会被修改,但线程依旧还会阻塞在synchronized上,等待着获取锁。在Java5中,同步器提供了**acquireInterruptibly(int arg)**方法,这个方法在等待获取同步状态时,如果当前线程被中断,会立刻返回,并排除InterruptedException(中断异常)

**独占式超时获取同步状态doAcquireNanos(int arg, long nanosTimeout)**和独占式获取同步状态acquire在流程上非常相似,其主要区别在于未获得同步状态时的处理逻辑。acquire(int arg)在未获取到同步状态时,将会使当前线程一直处于等待状态,而doAcquireNanos(int arg, long nanosTimeout)会使当前线程等待nanosTimeout纳秒,如果当前线程在nanosTimeout纳秒内没有获取到同步状态,将会从等待逻辑中自动返回

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/121395776
今日推荐