JAVA并发编程:详解AQS、显示锁Lock、ReentrantLock及源码分析

1、AbstractQueuedSynchronizer

1.1 学习 AQS 的必要性

  队列同步器 AbstractQueuedSynchronizer(以下简称同步器或 AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态 state,通过内置的 FIFO 队列来完成资源获取线程的排队工作。

1.2 AQS使用方式

  AQS 的主要使用方式是继承,子类通过继承 AQS 并实现它的抽象方法来管 理同步状态,在 AQS 里由一个 int 型的 state 来代表这个状态,在抽象方法的实 现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供以下 3 个方法来进行操作,因为它们能够保证状态的改变是安全的。

  • int getState():返回当前线程同步状态state的值
  • setState(int newState):设置同步状态state值,state属性被volatile修饰。
  • compareAndSetState(int expect, int update):如果当前状态state值value等于期望值expect,那么原子地设置同步状态state为给定update值。使用 CAS 设置当前状态,该方 法能够保证状态设置的原子性。
/**
 * The synchronization state.
*/
private volatile int state;

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

1.3 AQS中的设计模式

  同步器的设计基于模板方法模式。模板方法模式的意图是,定义一个操作中 的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。我们最常见的就是 Spring 框架里的各种 Template。

1.4 AQS中的方法

1.4.1 模板方法

  实现自定义同步组件时,将会调用同步器提供的模板方法。这些模板方法同步器提供的模板方法基本上分为以下3 类:独占式获取与释放同 步状态、共享式获取与释放、同步状态和查询同步队列中的等待线程情况。

  • acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法。
  • acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程未获取同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回。
  • tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException:在 acquireInterruptibly(int arg)方法的基础上加了超时限制,如果当前线程在超时时间内没有没有获取同步状态,则返回false,获取到则返回true。
  • acquireShared(int arg) :共享式获取同步状态,如果当前线程未获取同步状态,将会进入同步队列等待,与独占式获取的区别是在同一时刻有多个线程获取到同步状态。
  • acquireInterruptibly(int arg) throws InterruptedException :与acquireShared(int arg) 相同,该方法响应中断。
  • tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException:在acquireInterruptibly(int arg)的基础上增加了超时限制。
  • release(int arg):独占式的获取同步状态,该方法会在释放同步状态之后,将同步队列中等一个节点包含的线程唤醒。
  • releaseShared(int arg):共享式的释放同步状态。
  • Collection getQueuedThreads():获取等待在同步队列上的线程集合。

1.4.2 可重写的方法

  • tryAcquire(int arg):独占式获取同步状态,实现给方法需要查询查询当前状态并判断同步状态是否符合预期,然后再进行CAS操作设置同步状态。
  • tryRelease(int arg):独占式是否同步状态,等待获取同步状态的线程将有机会获取同步状态。
  • tryAcquireShared(int arg):共享式获取同步状态,返回大于等于0的值,表示获取成功,否则获取失败。
  • tryReleaseShared(int arg):共享式是否同步状态。
  • isHeldExclusively():当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占。

1.5 AQS中的数据结构-节点和同步队列

1.5.1 节点Node

  既然说Java 中的AQS 是CLH 队列锁的一种变体实现,毫无疑问,作为队列来说,必然要有一个节点的数据结构来保存我们前面所说的各种域,比如前驱节点,节点的状态等,这个数据结构就是AQS中的内部类Node。
作为这个数据结构应该关心些什么信息?

  • 线程信息,肯定要知道我是哪个线程。
  • 队列中线程状态,既然知道是哪一个线程,肯定还要知道线程当前处在什么状态,是已经取消了“获锁”请求,还是在“等待中”,或者说“即将得到锁”。
  • 前驱和后继线程,因为是一个等待队列,那么也就需要知道当前线程前面的是哪个线程,当前线程后面的是哪个线程(因为当前线程释放锁以后,理当立马通知后继线程去获取锁)。
    下面来看看这个Node类的设计,如下图所示:
    在这里插入图片描述
      从Node类中可以看出,其中包括了:线程的两种等待模式、线程在队列中的状态及一些成员变量。

线程的2种等待模式:

  • SHARED:表示线程以共享模式等待锁(如ReadLock)
  • EXCLUSIVE:表示线程独占模式等待锁(如ReetrantLock),独占模式就是一把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁。

线程在队列中的状态:

  • CANCELLED:值为1,表示线程的获锁请求已经“取消”。
  • SIGNAL:值为-1,表示该线程一切都准备好了,就等待锁空闲出来给此线程了。
  • CONDITION:值为-2,表示线程等待某一个条件(Condition)被满足。
  • PROPAGATE:值为-3,当线程处在“SHARED”模式时,该字段才会被使用上。

成员变量:

  • volatile int waitStatus:该int变量表示线程在队列中的状态,其值就是上述提到的CANCELLED、SIGNAL、CONDITION、PROPAGATE。
  • volatile Node prev:该变量类型为Node对象,表示该节点的前一个Node 节点(前驱)
  • volatile Node next:该变量类型为Node对象,表示该节点的后一个Node 节点(后继)
  • volatile Thread thread:该变量类型为Thread 对象,表示该节点的代表的线程
  • Node nextWaiter:该变量类型为Node对象,表示等待condition 条件的Node节点。

  当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。

1.5.2 同步队列

head 和tail:
  AQS 还拥有首节点(head)和尾节点(tail)两个引用,一个指向队列头节点,而另一个指向队列尾节点。
  因为首节点head 是不保存线程信息的节点,仅仅是因为数据结构设计上的需要,在数据结构上,这种做法往往叫做“空头节点链表”。对应的就有“非空头结点链表”。
在这里插入图片描述
节点加入到同步队列:
  当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步 状态,也就是获取同步状态失败,AQS 会将这个线程以及等待状态等信息构造成 为一个节点(Node)并将其加入同步队列的尾部。而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个基于 CAS 的设置尾节点的方法: compareAndSetTail(Node expect,Nodeupdate),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。
在这里插入图片描述
首节点的变化:
  首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会 唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。设 置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功 获取到同步状态,因此设置头节点的方法并不需要使用 CAS 来保证,它只需要将 首节点设置成为原首节点的后继节点并断开原首节点的 next 引用即可。
在这里插入图片描述

2、显示锁

  有了 synchronized 为什么还要 Lock?
  Java 程序是靠 synchronized 关键字实现锁功能的,使用 synchronized 关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。

显示锁的特性:

  • 尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
  • 能被中断地获取锁:与synchronized关键字不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
  • 超时获取锁:在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回空。

3、Lock接口

3.1 Lock接口常用API

  • void lock():获取锁,调用该方法当前线程将会获取锁,当锁获得后,从改方法返回。
  • void lockInterruptibly() throws InterruptedException:可中断地获取锁,和lock()方法不同之处在于该方法响应会中断,即在锁的获取过程中,可以中断当前线程。
  • boolean tryLock():尝试非阻塞地获取锁,调用该方法后立即返回,如果能够获取锁则返回true,否则返回false。
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException:超时获取锁,当前线程在以下3中情况会返回。当前线程在超时时间内获取得了锁,则返回true;当前线程在超时时间内被中断,则返回false;超时时间结束,则返回false。
  • void unlock():释放锁

3.2 Lock的标准用法

private static Lock lock = new ReentrantLock();
private static void lock(){
    lock.lock();
    try{
        System.out.println("execute business 。。。");
    }finally {
        lock.unlock();
    }
}

  在finally 块中释放锁,目的是保证在获取到锁之后,最终能够被释放。
  不要将获取锁的过程写在try 块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放。

4、可重入锁ReentrantLock、锁的公平和非公平

4.1 可重入锁

  可重入锁,简单来说就“同一个线程对于已经获得的锁,可以多次继续申请到该锁的使用权”。而synchronized 关键字隐式的支持可重入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁。ReentrantLock 在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
  重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞, 该特性的实现需要解决以下两个问题。
  1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程, 如果是,则再次成功获取。
  2)锁的最终释放。线程重复 n 次获取了锁,随后在第 n 次释放该锁后,其 他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示 当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于 0 时表示锁已 经成功释放。
  nonfairTryAcquire 方法增加了再次获取同步状态的处理逻辑:通过判断当前 线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请 求,则将同步状态值进行增加并返回 true,表示获取同步状态成功。同步状态表 示锁被一个线程重复获取的次数。
  如果该锁被获取了 n 次,那么前(n-1)次 tryRelease(int releases)方法必须返回 false,而只有同步状态完全释放了,才能返回 true。可以看到,该方法将同步状 态是否为 0 作为最终释放的条件,当同步状态为 0 时,将占有线程设置为null, 并返回 true,表示释放成功。

4.2 公平锁和非公平锁

  如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。 事实上,公平的锁机制往往没有非公平的效率高。
  ReentrantLock 的构造函数中,默认的无参构造函数将会把 Sync 对象创建为 NonfairSync 对象,这是一个“非公平锁”;而另一个构造函数 ReentrantLock(boolean fair)传入参数为 true 时将会把 Sync 对象创建为“公平锁” FairSync。
  nonfairTryAcquire(int acquires)方法,对于非公平锁,只要 CAS 设置同步状态 成功,则表示当前线程获取了锁,而公平锁则不同。tryAcquire 方法,该方法与 nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了 hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的 判断,如果该方法返回 true,则表示有线程比当前线程更早地请求获取锁,因此 需要等待前驱线程获取并释放锁之后才能继续获取锁。
  在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程A持有一个锁,并且线程B 请求这个锁。由于这个锁已被线程A 持有,因此B 将被挂起。当A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果C 也请求这个锁,那么C 很可能会在B 被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面:B 获得锁的时刻并没有推迟,C 更早地获得了锁,并且吞吐量也获得了提高。

5、ReentrantLock源码分析

  ReentrantLock类是基于AbstractQueuedSynchronizer(AQS或同步器,后续会专门讲解AQS相关知识点)队列同步器实现的,当调用ReentrantLock类的lock()方法时,会调用其内部对象Sync的lock()方法。
ReentrantLock默认是非公平锁,但是ReentrantLock的构造方法提供了一个参数可以指定锁是公平锁还是非公平锁。

// 默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

// 通过参数fair为true,可以指定获取的锁为公平锁
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

如下图所示,可以清晰的看到各类的关系。
在这里插入图片描述

5.1 公平锁 FairSync

5.1.1 lock()方法

  FairSync 类 lock()方法会调用ASQ中 acquire(int arg)方法,可以获取同步状态,主要完成了同步状态获取、节点构造、加入同步队列以及在同步队列中自旋等待的相关工作。
  首先调用自定义同步器实现的 tryAcquire(int arg)方法,该方法需要保证线程 安全的获取同步状态。
  如果同步状态获取失败(tryAcquire 返回 false),则构造同步节点(独占式 Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过 addWaiter(Node node)方法将该节点加入到同步队列的尾部。
  最后调用 acquireQueued(Node node,int arg)方法,使得该节点以“死循环” 的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒 主要依靠前驱节点的出队或阻塞线程被中断来实现。

public final void acquire(int arg) {
	 // 尝试获取锁,如果获取锁失败,则将当前线程执行信息放入到AQS内部的队列中
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         // 中断线程
         selfInterrupt();
 }

FairSync类中实现AQS中的 tryAcquire(int arg)方法,如下所示:

/**
  * Fair version of tryAcquire.  Don't grant access unless
  * recursive call or no waiters or is first.
  * 
  */
 protected final boolean tryAcquire(int acquires) {
 	 // 获取当前线程
     final Thread current = Thread.currentThread();
     // 获取线程同步状态state
     int c = getState();
     // 如果状态为0,表示当前锁是空闲的,可以获取锁
     if (c == 0) {
     	 // 判断当前线程是不是等待最久多线程,判断当前线程是不是在等待队列的第二个元素,因为队列head是当前(之前)拥有锁的线程,如果是等待最久的线程,则返回false;
     	 // 并调用compareAndSetState方法获取到锁
         if (!hasQueuedPredecessors() &&
             compareAndSetState(0, acquires)) {
             // 设置当前执行线程为当前线程current,目的是为了后续锁的可重入使用
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     // 如果线程同步状态state不为0,且当前线程current等于当前执行线程(独占模式同步的当前所有者)。
     else if (current == getExclusiveOwnerThread()) {
         int nextc = c + acquires;
         if (nextc < 0)
             throw new Error("Maximum lock count exceeded");
         setState(nextc); // 对同步状态state赋新值,
         // 由此可见ReentrantLock是一个可重入锁,但是重入多少次就得unlock多少次,保证最终的同步状态state为0
         return true;
     }
     return false;
 }

  公平锁中的hasQueuedPredecessors()方法,查询是否有任何线程在等待获取比当前线程更长的时间。

public final boolean hasQueuedPredecessors() {
     // The correctness of this depends on head being initialized
     // before tail and on head.next being accurate if the current
     // thread is first in queue.
     Node t = tail; // Read fields in reverse initialization order
     Node h = head;
     Node s;
     return h != t &&
         ((s = h.next) == null || s.thread != Thread.currentThread());
 }

  AQS中的addWaiter(Node mode)方法,如下所示:

private Node addWaiter(Node mode) {
	// 创建一个节点node
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    // 放在队列尾端,设置当前的尾节点,为新建节点的上级节点
    Node pred = tail;  //  设置前驱节点为尾结点
    if (pred != null) { // 如果尾结点存在
        node.prev = pred;  // 新加入的节点的前驱节点为之前的尾结点
        if (compareAndSetTail(pred, node)) { // 通过CAS操作设置尾结点
            pred.next = node; // 之前的尾结点,把新加入的节点设置为它的下一个节点
            return node;
        }
    }
    enq(node);
    return node;
}

  将当前线程包装成 Node 后,当前队列不为空的情况下,先尝试把当前节点加入 队列并成为尾节点,如果不成功或者队列为空进入 enq(final Node node)方法。

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;
            }
        }
    }
}

  在 enq(final Node node)方法中,同步器通过“死循环”来保证节点的正确添 加,这个死循环中,做了两件事,第一件,如果队列为空,初始化队列,new 出 一个空节点,并让首节点(head)和尾节点(tail)两个引用都指向这个空节点; 第二件事,把当前节点加入队列。 在“死循环”中只有通过 CAS 将节点设置成为尾节点之后,当前线程才能从该方法返回,否则当前线程不断地尝试设置。
  节点进入同步队列之后,再看看acquireQueued(final Node node, int arg)方法,如下所示:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	// 获取node的前驱结点,如果为空抛出异常
            final Node p = node.predecessor();
            // 如果node节点的上一节点等于头结点,且成功的获取了同步状态
            if (p == head && tryAcquire(arg)) {
                setHead(node); // 设置node为头节点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 线程进入阻塞状态,等待被唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

  其实就是一个自旋的过程,每个节点(或者说每个线程)都在自省地观察, 当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在 这个自旋过程中(并会阻塞节点的线程)。
  在 acquireQueued(final Node node,int arg)方法中,当前线程在“死循环”中 尝试获取同步状态,而只有前驱节点是头节点才能够尝试获取同步状态,这是为 什么?原因有两个。
  第一,头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状 态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱节 点是否是头节点。
  第二,维护同步队列的 FIFO 原则。
  当前线程获取到同步状态后,让首节点(head)这个引用指向自己所在节点。 当同步状态获取成功后,当前线程就从 acquire 方法返回了。如果同步器实现的 是锁,那就代表当前线程获得了锁。

5.1.2 unlock()方法

  unlock()方法会调用AQS中的release(int arg)方法,以独占模式发布。 如果调用 tryRelease(int arg)方法返回true,则通过解锁一个或多个线程来实现。

public final boolean release(int arg) {
	// 同步状态state-1,返回true
    if (tryRelease(arg)) {
    	// 获取头节点信息
        Node h = head;
        // 头节点不为空,waitStatus 不为 0
        if (h != null && h.waitStatus != 0)
        	// 唤醒头节点的下一节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

  调用 tryRelease(int releases)方法尝试释放锁,如果返回为空,则释放锁成功。

protected final boolean tryRelease(int releases) {
// 同步状态state-1
    int c = getState() - releases;
    //如果当前线程不等于执行器中的线程,抛出异常处理。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 同步状态state为0,表示释放锁成功。
    if (c == 0) {
        free = true;
        // 当前执行器中的线程设置为空
        setExclusiveOwnerThread(null);
    }
    // 设置同步状态
    setState(c);
    return free;
}

  执行release(int arg)该方法执行时,会唤醒首节点(head)所指向节点的后继节点线程, unparkSuccessor(Node node)方法使用 LockSupport 来唤醒处于等待状态的线程。

private void unparkSuccessor(Node node) { 
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 获取下一节点
    Node s = node.next;
    // 如果节点为null 或者节点处于被cancel状态,先从尾部开始遍历,找到需要被唤醒的节点线程
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);  // 唤醒节点
}

  这段代码的意思,一般情况下,被唤醒的是 head 指向节点的后继节点线程, 如果这个后继节点处于被 cancel 状态,(我推测开发者的思路这样的:后继节点 处于被 cancel 状态,意味着当锁竞争激烈时,队列的第一个节点等了很久(一直 被还未加入队列的节点抢走锁),包括后续的节点 cancel 的几率都比较大,所以) 先从尾开始遍历,找到最前面且没有被 cancel 的节点。

5.2 非公平锁 NonfairSync

5.2.1 lock()方法

  NonfairSync类中的lock()方法,首先通过CAS操作看是否能拿到锁,如果拿不到则调用AQS中的acquire(int arg)方法。

final void lock() {
    // CAS操作,设置同步状态,如果当前状态等于期望值,则设置成功。线程成功获取到锁。
    if (compareAndSetState(0, 1))
        // 设置当前拥有独占访问的线程(也就是设置当前执行线程为当前获取锁的线程)
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 如果获取锁失败,调用AQS中的acquire(int arg)方法
        acquire(1);
}

  调用ASQ中的 acquire(int arg)尝试获取锁,如果获取锁失败,则将当前线程的执行信息加入到AQS内部队列中。

public final void acquire(int arg) {
   // 尝试获取锁,如果获取锁失败,则将当前线程执行信息放入到AQS内部的队列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中断线程
        selfInterrupt();
}

  NonfairSync类重写tryAcquire(int acquires)方法,调用非公平锁的实现方法,尝试获取锁。

protected final boolean tryAcquire(int acquires) {
    // 调用非公平锁的实现方法,尝试获取锁。
    return nonfairTryAcquire(acquires);
}

  调用Sync类的 nonfairTryAcquire(int acquires)方法,尝试获取锁。

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取同步状态state
    int c = getState();
    // 如果同步状态等于0
    if (c == 0) {
        // CAS操作,设置同步状态,如果当前状态等于期望值,则设置成功。线程成功获取到锁。
        if (compareAndSetState(0, acquires)) {
            // 设置当前拥有独占访问的线程(也就是设置当前执行线程为当前获取锁的线程)
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 同步状态不等于0 ,当前线程current等于当前执行器中的线程
    else if (current == getExclusiveOwnerThread()) {
        // 设置同步状态state,由此可看出ReentrantLock是一个可重入锁,但特别注意的是,重入多少次就得unlock多少次,保证最终state为0
        int nextc = c + acquires;
        if (nextc < 0) // overflow
        throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

5.2.2 unlock()方法

  跟公平锁一致,在此不多赘述。


备注:博主微信公众号,不定期更新文章,欢迎扫码关注。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值