互联网公司Java开发面试题: 谈谈可重入锁ReentrantLock?

本文详细解读ReentrantLock的定义、使用示例、优势,包括可重入性、公平锁、手动控制和多条件绑定。跟随源码探讨AQS与ReentrantLock的关系,剖析加锁解锁机制,助力面试与实践

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在java工程师面试中, 并发包JUC几乎是必问题. 而ReentrantLock类作为JUC中一种常见的锁, 也是面试题中高频考点. 本文将从ReentrantLock的定义, 基本用法, 优点到源码分析, 由浅入深的讲解ReentrantLock, 助你斩获理想的offer.

ReentrantLock 定义

ReentrantLock 根据其jdk给的注释定义如下:

A reentrant mutual exclusion Lock

即代表ReentrantLock是可重入的互斥锁.

  • 可重入代表已加锁的线程, 可以重复加锁. 可重入的最大次数为int的最大值:2147483647
  • 互斥锁即代表只能有一个线程上锁成功, 其他线程想要加锁只能阻塞等待.

使用ReentrantLock示例

根据jdk给的ReentrantLock示例代码如下

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

即官方建议释放锁的操作放在finally 代码块中, 这样可以在一定程度上避免死锁.
在创建ReentrantLock 时, 默认的无参构造是创建一个非公平锁, 可以传递一个布尔值, 来设定是否为公平锁.

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

除了上面给出的lock方法外, 也可以使用tryLock , 传递一个等待加锁的时间, 返回一个布尔值.
加锁成功 则返回true, 失败返回false , 则代表到了设定的时间还是加锁失败.

  public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

使用ReentrantLock的好处

ReentrantLock提供了tryLock可设置等待时长

例如上文提到的ReentrantLock提供了可设置等待时长的方法, tryLock(long timeout, TimeUnit unit), 如果到时间了还没加锁成功, 则放弃加锁, 这样也能节省服务器资源.

ReentrantLock可以设置是否公平锁

java中的synchronized关键字只能是非公平的, 是一种抢占式的加锁.
而ReentrantLock中可以在构造方法设置是否为公平锁. 使用更加灵活.
其公平锁主要实现思路为在加锁的方法执行之前, 一定会先去判断当前的阻塞队列中是否有线程在排队, 如果有, 则只能是队列中的第一个线程加锁成功.

可手动加锁和释放锁

相比较于synchronized 只能jvm去释放锁, ReentrantLock 可以自己通过调用方法去手动的加锁和释放锁, 释放锁一般是写在finally代码块中. 这也存在一定的弊端, 如果没有调用unlock方法 ,则会造成死锁, 因此在使用 ReentrantLock 时, 一定加锁和释放锁配对使用.

ReentrantLock可实现多条件的绑定

一个ReentrantLock对象, 可以绑定多个Condition , 通过调用await和signal方法, 来进行线程的精确唤醒.
而synchronized只能要么随机唤醒一个线程, 要么唤醒所有的线程.

ReentrantLock的原理 AQS

ReentrantLock与AQS的关系

ReentrantLock默认的构造方法如下 :

   public ReentrantLock() {
        sync = new NonfairSync();
    }

可以看到实际上是new了一个NonfairSync 对象.
NonfairSync 对象的源码如下, 它实际上是继承了Sync 类.

 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

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

Sync 类 的源码截图如下, Sync 本身是ReentrantLock 一个内部抽象类, 提供了很多模板方法.
它实际上是继承了抽象类AbstractQueuedSynchronizer , 而AbstractQueuedSynchronizer其实就是所说的AQS. 因此ReentrantLock 是基于AQS的.
在这里插入图片描述
AbstractQueuedSynchronizer的核心主要是node, 自定义的一个内部类, 双向链表(实现的一个阻塞等待队列), state用于实现加锁, 释放锁.

一图简单了解AQS


AQS的结构如上图所示. 流程如下

  1. 线程1 加锁的时候, aqs会去判断state 是否为0 , 如果是0 , 代表没有线程加锁, 则线程1可以加锁, state变量进行cas操作给加1, 并且会维护当前加锁的线程为1. 如果线程1 重复加锁, 则state会累加1.
  2. 线程2加锁的时候, 此时aqs判断state不为0, 则线程2加锁失败, 进入aqs的队列, 进行等待入队.
  3. 线程1 释放锁, 当释放到state 为0的时候, 会唤醒队列中阻塞等待的线程.
  4. 线程2被唤醒, 出队列去尝试加锁, 此时state为0 , 加锁成功, 当前加锁的线程变为线程2.
AQS中的state如何实现加锁

根据ReentrantLock 默认的构造方法的内部类NonfairSync 如下的源码, 加锁的时候, 调用lock方法, NonfairSync类对其进行了重写.

 static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    }

先是去执行compareAndSetState 方法, 该方法为AQS的类中的方法. 具体实现如下. 可以看到其是调用jdk底层的Unsafe 类的cas方法, 去进行state变量的操作了. cas是一种无锁化的操作, 提升了加锁的性能.

 protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

回到NonfairSync 类, 如果成功把state修改为1了, 则代表加锁成功, 则会执行setExclusiveOwnerThread 方法, 即设置当前加锁的线程. 该方法为AbstractOwnableSynchronizer 类中的方法.
在这里插入图片描述

AQS中的state如何实现可重入加锁的

当线程1加锁成功后, 再次尝试加锁时, 由于此时state不为0 ,则206行的if判断为false ,则会走209行的代码, 执行acqure方法, 传递的参数为1.

在这里插入图片描述
acquire代码如下, 是一个if判断, 先执行tryAcquire方法, 传递进去1.

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire方法 在aqs中是一个空的方法, 需要看子类的实现.
在这里插入图片描述
实现的方法在ReentrantLock类中的内部类NonfairSync中, 执行nonfairTryAcquire方法.
在这里插入图片描述
该方法的代码如下:

final boolean nonfairTryAcquire(int acquires) {
//   获取当前加锁线程. 
            final Thread current = Thread.currentThread();
            //  获取state的值
            int c = getState();
            // 此处判断c是否等于0 ,是为了一开始有线程加锁, 但之后又释放锁, 因此此处再次去尝试加锁. 
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 此处判断加锁的线程是否为当前线程
            else if (current == getExclusiveOwnerThread()) {
                // 是当前线程则把stata加1  c为state的值, acquires为1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                  // 设置state的最新值
                setState(nextc);
                // 返回true 结束返回
                return true;
            }
            return false;
        }

上面的方法, 就是基于aqs的state实现可重入锁的核心. 具体逻辑已写注释.
主要流程为判断当前加锁的线程是否为之前已经加锁的线程, 如果是则把state值加1
由于tryAcquire返回的是true ,!tryAcquire的值是false, 那么此if判断就会直接结束, 可重入加锁的过程结束.

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
加锁失败时AQS中的队列介绍

如果此时已经有线程1加锁了, 此时线程2再过来尝试加锁, 根据上一小节提到的流程, 则线程2会执行如下的acquire方法. 并且tryAcquire方法会返回false, 因为只有加锁的是当前线程tryAcquire才会返回true.
那么接着会去执行acquireQueued方法里面的addWaiter方法

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

addWaiter方法是AQS提供的提供的方法, 其源码如下

  private Node addWaiter(Node mode) {
 		// 把当前线程和node的模式作为Node类的构造方法的参数进行
 		//Node节点的创建. mode 根据上一步传递过来的值为EXCLUSIVE 独占锁模式
       Node node = new Node(Thread.currentThread(), mode);
       // Try the fast path of enq; backup to full enq on failure
       // 获取最后一个Node节点
       Node pred = tail;
       if (pred != null) {
       // 如果队列中最后一个节点不为空, 那么则进行cas操作, 把当前节点设置为队列中最后一个
           node.prev = pred;
           if (compareAndSetTail(pred, node)) {
               pred.next = node;
               return node;
           }
       }
       // 入队列放入最后一个.  
       enq(node);
       return node;
    }

在上面的方法中, 有一个关键的类Node , 该类为AQS的内部类, 用于实现一个双向链表来进行排队加锁.
Node 类中, 有如下的几个核心成员变量

/**
 node 阻塞的状态. 分为多钟状态 
  CANCELLED =  1;  // 取消
   SIGNAL    = -1;  // 继任者需要唤醒
CONDITION = -2;  // 线程正在等待状态
PROPAGATE = -3; // 
**/
volatile int waitStatus;
// 节点的上一个节点
volatile Node prev;
// 节点的下一个节点
volatile Node next;
// 当前节点的线程
 volatile Thread thread;

通过画图, 此结构大致如下:
每个node 节点, 收尾相连, 实现一个队列.

加锁失败时入队列详解
加锁失败时addWaiter方法详解

上一节中, 简单提到了当有第二个线程来加锁的时候, 会执行如下的addWaiter方法

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

Node pred = tail; 这一行代码会去拿到当前的队列的尾节点, 会判断是否为空, 如果不为空, 则会进行一个cas操作, 把当前新创建的node作为尾节点.
此时一开始队列中肯定是空的, 那么则会直接执行enq(node) 方法.
该方法如下:

  private Node enq(final Node node) {
        for (;;) {
        //  获取队列末尾的节点
            Node t = tail;
            if (t == null) { 
            //如果队列末尾的节点为空, 则创建一个新的Node
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
              //  第二次循环的时候, 进入此处代码
              // 此时末尾的节点不为空, 把t指向第一个空节点
                node.prev = t;
                // 进行cas操作, 把尾节点改成当前要插入的节点
                if (compareAndSetTail(t, node)) {
                  // 把第一个空节点的next指针指向插入的节点
                    t.next = node;
                    // 返回了第一个节点, 结束返回. 
                    return t;
                }
            }
        }
    }

上面这段代码, 一开始执行如下的逻辑 .
先获取尾节点, 此时由于队列中是空的, 那么则走一个cas , new 一个空的node对象.

 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } 
        }
    }

如下图所示, 此时队列中, 只有一个node , 并且head和tail 都指向该node.

由于进入的是for (;;) 循环, 且没有return , 那么还会进入第一次 循环, 此时就会走如下的这段代码

  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;

            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }

还是先获取尾节点, 此时尾节点为上一次循环创建的一个空节点.
node.prev = t 的含义是把要插入的节点的prev指针, 指向第一个节点, 因为此时t指向的是第一个节点. 如下图所示的绿色这一步
在这里插入图片描述
compareAndSetTail(t, node) 方法则是进行cas操作 , 把要插入的节点, 设置为尾节点. 设置完成之后, 则tail指针会指向新插入的节点.
即如下图的操作

执行完cas操作后, 执行t.next = node;这一行代码, 此行代码的含义是把第一个节点的next执行, 指向插入节点的, 形成一个双向链表. 即如下图所示的操作.
在这里插入图片描述
最后return 结束了方法, 返回了第一个节点.
回到addWaiter方法, 此时会执行 return node; 这行代码, node是新插入的节点.

加锁失败时acquireQueued方法详解

回到acquire 方法如下, addWaiter 方法执行完成后, 则会执行acquireQueued方法

  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

acquireQueued 方法如下 : 传递进来的是新插入的node , arg的值为1.

   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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

主要的逻辑为如下for循环. 一行一图来进行分析.

for (;;) {
      final Node p = node.predecessor();
      if (p == head && tryAcquire(arg)) {
          setHead(node);
          p.next = null; // help GC
          failed = false;
          return interrupted;
      }
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
          interrupted = true;
  }

node.predecessor() 方法,其实现是如下, 其实就返回前面一个节点.

 final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }


接着在for循环中, 执行了 if (p == head && tryAcquire(arg)) 这行if判断, 此时p根据上图确实是head节点, 那么则会去执行tryAcquire 方法.
tryAcquire 方法则是上文提到过的NonfairSync 类自己实现的非公平锁去进行尝试加锁的nonfairTryAcquire方法, 此处不再赘述.
由于此时线程1还没有释放锁, 线程2去执行尝试加锁, 返回的也会是false. 因此此处的if判断为false.

那么接下来在acquireQueued 方法中, 则会执行如下的代码. 进入到一个if判断.
if判断一开始会去执行shouldParkAfterFailedAcquire 方法. 把node的前一个节点, 和当前节点作为参数进行传递进去 .

    if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;

shouldParkAfterFailedAcquire 方法的内容如下. 主要的逻辑是判断pred节点的waitStatus 值是多少, 去执行不同的逻辑. 根据上面的分析, pred节点是new出来的Node, 当时并没有指定其waitStatus的值是多少, 那么则会取int的初始值为0 , 则会去执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 方法

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // Node.SIGNAL = -1 
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

compareAndSetWaitStatus 方法内容如下, 是进行cas操作, 把第一个节点的waitStatus 的值设置为 Node.SIGNAL , 并且返回一个false

    private static final boolean compareAndSetWaitStatus(Node node, int expect,int update) {
       return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
    }

设置完waitStatus值后, 回到如下的for循环, 由于shouldParkAfterFailedAcquire方法返回的是false, 此时还没有代码执行return操作, 因此, 还会继续循环下面的代码, 依然是获取第一个节点, tryAcquire方法去加锁依然会失败, 会再次执行
shouldParkAfterFailedAcquire 方法

for (;;) {
      final Node p = node.predecessor();
      if (p == head && tryAcquire(arg)) {
          setHead(node);
          p.next = null; // help GC
          failed = false;
          return interrupted;
      }
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
          interrupted = true;
  }

再次进入到shouldParkAfterFailedAcquire 方法后, 由于上一轮的循环中, 已经把pred节点的waitStatus设置成为了SIGNAL , 那么则会直接返回true.

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // Node.SIGNAL = -1 
        if (ws == Node.SIGNAL)
            return true;
        return false;
    }

shouldParkAfterFailedAcquire方法返回true之后, 则会执行上一步for循环if判断中的parkAndCheckInterrupt方法
该方法内容如下: 主要的操作就是执行 LockSupport.park 方法, 把第二个尝试加锁的线程, 进行挂起, 等待第一个线程释放锁的时候, 执行LockSupport.unpark方法去唤醒该线程.

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
第三个线程尝试加锁详解

根据上面的流程, 此时的状况入下图所示, 线程A加锁成功, 线程B被park挂起
在这里插入图片描述
假如再有第三个线程来进行尝试加锁, 根据上面的分析, 则会走到AQS的addWaiter 方法中去. 如下所示.

    private Node addWaiter(Node mode) {
        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)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

Node node = new Node(Thread.currentThread(), mode) 这行代码则是把线程3 封装为一个node对象, 接着获取尾节点, 此时尾节点不为空.
那么则执行node.prev = pred 这一行代码.
即把第三个线程的node的prev指向第二个线程的node对象.
如下图所示

接着执行compareAndSetTail(pred, node) cas操作, 把尾节点设置成线程C. 则变成如下的情况:
在这里插入图片描述
最后执行 pred.next = node; 来结束addWaiter方法. 把线程B的next指向第三个线程的node. 如下图所示.
在这里插入图片描述
接着去执行AQS的acquireQueued 方法. 与上面提到的步骤一样, 在for循环中, 设置线程B的waitStatus 的值为SIGNAL , 接着线程C执行 LockSupport.park(this) 方法被挂起.
此时情形如下图所示.线程B和C都被挂起.
在这里插入图片描述

基于state释放锁详解

释放锁调用unlock方法, 该方法如下

    public void unlock() {
        sync.release(1);
    }

release方法如下

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

首先回调用tryRelease方法. 该方法在AQS中是一个空的方法

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

需要找到其子类的实现. 在ReentrantLock类的Sync内部类中对该方法进行了实现, 代码如下:

 protected final boolean tryRelease(int releases) {
	   // relases的值为1 , 把state的值减1 
	   int c = getState() - releases;
	   // 校验当前释放锁的线程是否为加锁的线程, 不是则抛异常
	   if (Thread.currentThread() != getExclusiveOwnerThread())
	       throw new IllegalMonitorStateException();
	   boolean free = false;
	   // 判断state的值是否为0
	   if (c == 0) {
	       free = true;
	       // 释放锁完成, 把当前加锁的线程设置为null
	       setExclusiveOwnerThread(null);
	   }
	   // 设置state最新的值,并且state是被volatile修饰的, 保证可见性
	   setState(c);
	   // 如果state为0 则返回true, state不为0 , 返回false
	   return free;
	}
释放锁后唤醒队列中其他线程进行加锁详解

在上一小节中的release 方法中, 假如此时其他线程已经完全释放锁了, 即state为0了, 那么tryRelease 返回的则是true, 则会走下面node相关的代码.

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

Node h = head;是获取当前队列中的头节点, 并把值给h. 即如下图这一步

接着来了一个if判断 if (h != null && h.waitStatus != 0)
此时h是不等于空的, 并且h.waitStatus 的值是signal 为-1, 是不等于0 的, 因此if判断为true, 那么 则会走 unparkSuccessor(h) 方法, 并且传递的是头节点, 此节点是没有要加锁的阻塞等待的线程的, 它的下一个节点才有.

unparkSuccessor 方法的内容如下, 根据其注释此方法会唤醒其下一个节点, 即线程B

/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        // 获取第一个节点的waitStatus 
        int ws = node.waitStatus;
        if (ws < 0)
        // 如果小于0, 则cas把它设置为0 . 此时由于是signal(-1)因此会走此逻辑
            compareAndSetWaitStatus(node, ws, 0);
			
			// 获取头节点的下一个, 即线程B的节点
        Node s = node.next;
        //s不是null, 并且waitStatus 的值是-1,不会走此if里面的逻辑
        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;
        }
        // s不是null ,则唤醒队列中第二个节点的线程
        if (s != null)
            LockSupport.unpark(s.thread);
    }

unparkSuccessor 方法中, 一开始是把头节点的waitStatus的值设置为了0.
接着 Node s = node.next 获取了头节点的下一个节点. 给了变量s.
s所指向的节点不是空的, 则唤醒了s节点所在的线程.

根据前面的其他线程加锁失败的分析, 线程B是在acquireQueued 方法里的parkAndCheckInterrupt 方法被挂起的, 在上面一步被唤醒后, 则会继续执行for循环里面的方法, 去进行加锁.

   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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

此时在执行if (p == head && tryAcquire(arg)) 的时候, p是等于head的, 并且tryAcquire 也会返回true, 因为其他线程已经释放了锁.
则会执行如下的if中的代码

  if (p == head && tryAcquire(arg)) {
    setHead(node);
            p.next = null; // help GC
            failed = false;
            return interrupted;
}

先执行setHead(node) 方法, 该方法内容如下:

   private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

head = node , 即把当前节点指向head. 如下图所示
在这里插入图片描述
node.thread = null; 把线程B的node线程, 设置为null ,即设置为一个空的node.
node.prev = null 把线程B的node不指向第一个节点.
如下图所示:
在这里插入图片描述
接着再回到下面这段代码, 执行 p.next = null , p是第一个节点, 把第一个节点的next设置为null

final Node p = node.predecessor();
  if (p == head && tryAcquire(arg)) {
    setHead(node);
            p.next = null; // help GC
            failed = false;
            return interrupted;
}

如下图所示, 那么第一个节点则与双向链表失去了关联, 会被jvm垃圾回收掉. 原有的线程B的节点, 现在变成了头节点, 线程B加锁的过程也就完成了加锁.
在这里插入图片描述

好了, 以上就是对ReentrantLock从定义, 到其底层AQS源码的讲解, 希望你也能跟着源码走一边, 并且画图. 这样才能在面试中和在工作中使用ReentrantLock时, 更加的胸有成竹.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值