只是不愿随波逐流 ...|

lidongdongdong~

园龄:2年7个月粉丝:14关注:8

37、Lock(中)

内容来自王争 Java 编程之美

上一节我们讲解了JUC Lock 的各种特性,比如:支持重入锁、公平锁、可中断锁、非阻塞锁、可超时锁,本节我们就来讲一下 JUC Lock 的底层实现原理
JUC Lock 底层主要依赖 AQS 来实现,AQS 也是 JUC 中非常重要的基础组件
JUC 中很多锁(Lock、ReadWriteLock)和同步工具(Condition、Semaphore、CountDownLatch)都是基于 AQS 来实现的
因此在讲解 JUC Lock 的底层实现原理时,我们会重点讲解 AQS(Abstract Queued Synchronizer 抽象队列同步器)

1、AQS 简介

AQS 是抽象类 Abstract Queue Synchronizer 的简称,中文翻译为抽象队列同步器

前面讲到,在 Hotspot JVM 中,synchronized 主要依赖 ObjectMonitor 类来实现,类中的 _cxq、_EntryList、_WaitSet 用来排队线程
其中 _cxq、_EntryList 用来实现锁,也就是 synchronized,_WaitSet 用来实现条件变量,也就是 wait() 和 notify()
实际在功能上,AQS 跟 ObjectMonitor 非常类似,都实现了:排队线程、阻塞线程、唤醒线程等功能

class ObjectMonitor {
void *volatile _object; // 该 Monitor 锁所属的对象
void *volatile _owner; // 获取到该 Monitor 锁的线程
ObjectWaiter *volatile _cxq; // 没有获取到锁的线程暂时加入 _cxq, 单链表, 负责存操作
ObjectWaiter *volatile _EntryList; // 存储等待被唤醒的线程, 双链表, 负责取操作
ObjectWaiter *volatile _WaitSet; // 存储调用了 wait() 的线程, 双链表
}

不过在实现思路上,AQS 跟 ObjectMonitor 有所不同

  • ObjectMonitor 类是在 JVM 中基于 C++ 来实现的,因为 synchronized、wait()、notify() 是 Java 语言提供的内置的语法和函数
    AQS 类是在 JDK 中基于 Java 语言实现的,因为 JUC 只是 JDK 中的一个并发工具包而已
  • ObjectMonitor 使用不同的队列来实现锁和同步工具,AQS 使用同一个队列来实现锁和同步工具

接下来,我们就详细讲解一下 AQS 的实现原理

2、数据结构

AQS 类中所包含的成员变量并不多,如下代码所示,这几个成员变量构成了 AQS 实现锁和同步工具所依赖的核心数据结构

public abstract class AbstractOwnableSynchronizer {
private transient Thread exclusiveOwnerThread; // 独占所有者线程
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
}

如上代码所示,AQS 继承自 AbstractOwnableSynchronizer 类
AbstractOwnableSynchronizer 类只包含一个成员变量 exclusiveOwnerThread,AQS 连带继承来的一个成员变量,总共有 4 个成员变量

一个线程获取锁,无非就是对 state 变量进行 CAS 修改,修改成功则获取锁,修改失败则进入队列
而 AQS 就是负责线程进入同步队列以后的逻辑,如何出入队列?如何阻塞?如何唤醒?一切的核心都在 AQS 里

2.1、state

前面在讲到 synchronized 的底层实现原理时我们讲到:当多个线程竞争锁时,它们会通过 CAS 操作来设置 ObjectMonitor 中的 _owner 字段,谁设置成功,谁就获取了这个锁
AQS 中的 state 的作用就类似于 ObjectMonitor 中的 _owner 字段
只不过 _owner 字段是一个指针,存储的是获取锁的线程,而 state 是一个 int 类型的变量,存储 0、1 等整型值

  • 0 表示锁没有被占用
  • 1 表示锁已经被占用
  • 大于 1 的数表示重入的次数

当多个线程竞争锁时,它们会通过如下所示的 CAS 操作来更新 state 的值
这里 CAS 指的是:先检查 state 的值是否为 0,如果是的话,将 state 值设置为 1,谁设置成功,谁就获取了这个锁

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

compareAndSetState() 函数底层使用 Unsafe 类提供的 native 函数来实现
native 函数是 JVM 中的 C++ 函数,如果想要阅读 native 函数的代码实现,那么我们需要查看 JVM 源码
实际上 compareAndSetState() 函数经过层层调用,最底层仍然是依靠硬件提供的原子 CAS 指令来实现

2.2、exclusiveOwnerThread

AQS 中的 exclusiveOwnerThread 成员变量存储持有锁的线程,它配合 state 成员变量,可以实现锁的重入机制,关于重入机制的实现方式,我们稍后讲解

2.3、head 和 tail

在 ObjectMonitor 中,_cxq、_EntryList 用来存储等待锁的线程,_WaitSet 用来存储调用了 wait() 函数(等待条件变量的函数)的线程
相比而言,AQS 只有一个等待队列,既可以用来存储等待锁的线程,又可以用来存储等待条件变量的线程

在 ObjectMonitor 中,_cxq 使用单链表来实现,_EntryList 和 _WaitSet 使用双向链表来实现
在 AQS 中,等待队列使用双向链表来实现,双向链表的节点定义如下所示,AQS 中的 head 和 tail 两个成员变量分别为双向链表的头指针和尾指针

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;
volatile Thread thread;
volatile Node prev;
volatile Node next;
volatile int waitStatus;
Node nextWaiter;
}

3、基本原理

3.1、AQS 模板方法模式

AQS 使用模板方法模式来实现,在《设计模式之美》一书中我们讲到,模板方法模式包含两个主要的组件:模板方法和抽象方法
模板方法包含主功能逻辑,并且依赖抽象方法来实现部分逻辑的可定制化
当使用模板方法模式时,我们需要定义一个子类,让其继承模板类,并实现其中的抽象方法,然后再使用子类创建对象,调用对象的模板方法来做编程开发
AQS 的代码结构和使用方法大致也是如此

1、AQS 定义了 8 个模板方法,如下所示

以下 8 个函数可以分为 2 组,分别用于 AQS 的两种工作模式:独占模式和共享模式,其中前 4 个函数用于独占模式,后 4 个函数用于共享模式

  • Lock 为排它锁,因此 Lock 的底层实现只会用到 AQS 的独占模式
  • ReadWriteLock 中的读锁为共享锁,写锁为排它锁,因此 ReadWriteLock 的底层实现既会用到 AQS 的独占模式,又会用到 AQS 的共享模式
  • Semaphore、CountdownLatch 这些同步工具只会用到 AQS 的共享模式
// 独占模式
public final void acquire(int arg) {
// ...
}
public final void acquireInterruptibly(int arg) throws InterruptedException {
// ...
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
// ...
}
public final boolean release(int arg) {
// ...
}
// 共享模式
public final void acquireShared(int arg) {
// ...
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// ...
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
// ...
}
public final boolean releaseShared(int arg) {
// ...
}

2、AQS 提供了 4 个抽象方法,如下所示
加锁和解锁,就是利用 CAS 对 state、exclusiveOwnerThread 进行操作
由模板方法处理其它逻辑(同步队列):加锁失败后的添加队列和阻塞线程等(当前)、解锁成功后的删除队列和唤醒线程等(其它)

前两个抽象方法用于独占模式的 4 个模板方法,后两个抽象方法用于共享模式的 4 个模板方法

在标准的模板方法模式的代码实现中,抽象方法需要使用 abstract 关键字来定义,以强制子类去实现它
但以下抽象方法并没有使用 abstract 关键字来定义,而是给出了默认的实现,即抛出 UnsupportOperationException 异常
这样做是为了减少开发量,即我们不需要在子类中实现所有的抽象方法,用到哪个就实现哪个即可

// 用于独占模式的 4 个模板方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 用于共享模式的 4 个模板方法
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}

3.2、ReentrantLock 的 lock() 和 unlock()

ReentrantLock 既支持非公平锁,又支持公平锁,其部分代码如下所示
ReentrantLock 定义了两个继承自 AQS 的子类:NonfairSync 和 FairSync,分别用来实现非公平锁和公平锁
因为 NonfairSync 和 FairSync 的释放锁的逻辑是一样的,所以 NonfairSync 和 FairSync 又抽象出了一个公共的父类 Sync
注意:为了更清晰的展示原理,在不改变代码逻辑的情况下,我对本节中的代码均做了少许调整

public class ReentrantLock implements Lock {
private final Sync sync;
// 父类 Sync 继承 AQS
abstract static class Sync extends AbstractQueuedSynchronizer {}
// 非公平锁
static final class NonfairSync extends Sync {}
// 公平锁
static final class FairSync extends Sync {}
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
// ... 省略其他方法 ...
}

ReentrantLock 中

  • lock() 函数调用 AQS 的 acquire() 模板方法来实现
  • unlock() 函数调用 AQS 的 release()模板方法来实现

接下来我们就来看下 acquire() 和 release() 的底层实现原理

3.3、acquire() 模板方法

acquire() -> tryAcquire() -> addWaiter() -> acquireQueued()

acquire() 的代码实现如下所示,实现看似非常简单,实际上其包含的逻辑可不少,acquire() 先调用 tryAcquire() 方法去竞争获取锁

  • 如果 tryAcquire() 获取锁成功:acquire() 就直接返回
  • 如果 tryAcquire() 获取锁失败:执行 addWaiter(),将线程包裹为 Node 节点放入等待队列的尾部,最后调用 acquireQueued() 阻塞当前线程

selfInterrupt() 用来处理中断,如果在等待锁的过程中,线程被其他线程中断,那么在获取锁之后,将线程的中断标记设置为 true,这里的中断不是重点,简单了解即可

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

1、tryAcquire() 竞争获取锁

tryAcquire() 是抽象方法,在 NonfairSync 和 FairSync 中实现,代码如下所示,我对代码做了详细的注释,这里就不再重述其中的代码逻辑了
两个 tryAcquire() 方法的代码实现区别也不大,唯一的区别是:在获取锁之前,FairSync 会调用 hasQueuedPredecessors() 函数,查看等待队列中是否有线程在排队
如果有,那么 tryAcquire()返回 false,表示竞争锁失败,从而禁止 "插队" 获取锁的行为

// 非公平锁
static final class NonfairSync extends Sync {
// 尝试获取锁, 成功返回 true, 失败返回 false, AQS 用于实现锁时, acquires = 1
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取 state 值
// 1、锁没有被其他线程占用
if (c == 0) {
// CAS 设置 state 值为 1
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); // 设置 exclusiveOwnerThread
return true; // 获取锁成功
}
}
// 2、锁可重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; // state + 1
// 重入次数太多, 超过了 int 最大值, 溢出为负数, 此情况罕见
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc); // state = state + 1, state 记录重入的次数, 解锁的时候用
return true; // 获取锁成功
}
// 3、锁被其他线程占用
return false;
}
}
// 公平锁
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 1、锁没有被占用
if (c == 0) {
if (!hasQueuedPredecessors() && // 等待队列中没有线程时才获取锁
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 2、锁可重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 3、锁被其他线程占用
return false;
}
}

2、addWaiter() 将线程包裹为 Node 节点放入等待队列的尾部

addWaiter() 函数的代码实现如下所示,在多线程环境下,往链表尾部添加节点会存在线程安全问题
因此下面的代码采用自旋 + CAS 操作的方式来解决这个问题,这种方式在 AtomicInteger 等原子类中被大量使用,我们在讲解原子类时再详细讲解
除此之外,addWaiter() 函数还需要特殊处理链表为空的情况,同样也存在线程安全问题,也同样是采用自旋 + CAS 操作解决的
注意:为了方便操作,AQS 中的双向链表带有虚拟头节点,关于虚拟头节点,你可以阅读我的《数据结构与算法之美》这本书来了解

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 自旋执行 CAS 操作, 直到成功为止
for (; ; ) {
Node t = tail;
// 链表为空, 添加虚拟头节点
if (t == null) {
// CAS 操作解决添加虚拟头节点的线程安全问题
if (compareAndSetHead(null, new Node())) tail = head;
}
// 链表不为空
else {
node.prev = t;
// CAS 操作解决了同时往链表尾部添加节点时的线程安全问题
if (compareAndSetTail(t, node)) {
t.next = node;
return node;
}
}
}
}

3、acquireQueued() 阻塞当前线程,这个方法是最重要的

acquireQueued() 的代码实现如下所示,主要包含两部分逻辑:使用 tryAcquire() 函数来竞争锁和使用 park() 函数来阻塞线程,并且采用 for 循环来交替执行这两个逻辑
之所以这样做,是因为线程在被唤醒(取消阻塞)之后,并不是直接获取锁,而是需要重新竞争锁,如果竞争失败,那么就需要再次被阻塞
关于代码中涉及的中断的处理逻辑,我们在本节中的中断机制小结中讲解(如果线程被中断唤醒,继续自旋阻塞,即不对中断做处理)

final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋(竞争锁 + 阻塞), 因为被唤醒之后不一定能竞争到锁, 所以要自旋
for (; ; ) {
final Node p = node.predecessor(); // p 是 node 的上一个节点
// 只有前驱节点是头节点的才能尝试获取同步状态
// 1、头节点既是虚拟头节点,又是成功获取到同步状态的节点
// 而头节点的线程释放了同步状态后,将会唤醒后继节点
// 后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点
// 如果线程是被中断唤醒的, 那么 p 就不一定等于 head, 也就不能去竞争锁
// 2、维护同步队列的 FIFO 原则
if (p == head && tryAcquire(arg)) {
// 成功获得锁
// 设置首节点是通过获取同步状态成功的线程完成的
// 由于只有一个线程成功获取到同步状态,因此设置头节点的方法并不需要 CAS 来保证
setHead(node); // 把 node 设置成虚拟头节点, 也就相当于将它删除, 头节点是成功获取到同步状态的节点
p.next = null; // help GC
failed = false;
return interrupted;
}
// 调用 park() 函数来阻塞线程, 线程被唤醒有以下两种情况
// 1、其他线程调用 unpark() 函数唤醒, 此时节点位于虚拟头节点的下一个, p == head
// 2、被中断唤醒, 此时节点不一定是虚拟头节点的下一个, p 不一定等于 head
if (parkAndCheckInterrupt()) interrupted = true;
}
} finally {
// 以上过程只要抛出异常, 都要将这个节点标记为 CANCELLED, 等待被删除
if (failed) cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
// 底层调用 JVM 提供的 native park() 函数来实现, 跟 synchronized 使用的 park() 函数相同
// 阻塞当前线程,只有调用 unpark(Thread thread) 方法或者当前线程被中断,才能从 park() 方法返回
// 参数 Object blocker,用来标识当前线程在等待的对象(阻塞对象),主要用于问题排查和系统监控
LockSupport.park(this);
return Thread.interrupted();
}

image

3.4、release() 模板方法

release() -> tryRelease() -> unpark()

release() 模板方法的代码实现比较简单,如下所示,主要包含两部分逻辑:使用 tryRelease() 函数释放锁、调用 unpark() 函数唤醒链表首节点(除虚拟头节点之外)对应的线程

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 内部调用 unpark() 函数, LockSupport.unpark(h.next.thread);
return true;
}
return false;
}

tryRelease() 是抽象方法,不管是公平锁还是非公平锁,tryRelease() 释放锁的逻辑相同,如下所示,代码中有详细的注释,这里就不再赘述代码逻辑了

static final class Sync extends AbstractQueuedSynchronizer {
// 释放锁, 成功返回 true, 失败返回 false, AQS 用于实现锁时, releases = 1
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // state - 1
// 不持有锁的线程去释放锁, 这不是瞎胡闹嘛, 抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException();
// state - 1 之后为 0, 解锁
if (c == 0) {
setExclusiveOwnerThread(null);
return true;
}
setState(c); // state - 1 之后不为 0, 说明锁被重入多次, 还不能解锁
return false;
}
}

3.5、总结

从上述分析我们可以发现

  • 模板方法 acquire() 包含加锁的所有逻辑,比如:竞争锁、竞争失败之后的排队、阻塞
    而竞争锁这部分逻辑由抽象方法 tryAcquire() 来实现,因此我们可以在子类中定制如何竞争锁,比如:是否支持重入锁、是否支持公平锁等
  • 模板方法 release() 包含解锁的所有逻辑,比如:释放锁、唤醒等待线程
    而释放锁这部分逻辑由抽象方法 tryRelease() 来实现,因此我们也可以在子类中定制如何释放锁

独占式同步状态的获取和释放

  • 在获取同步状态时,同步器维护一个同步队列
    获取状态失败的线程都会被加入到队列中并在队列中进行自旋
    移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态
  • 在释放同步状态时,同步器调用 tryRelease(int arg) 方法释放同步状态,然后唤醒头节点的后继节点

4、中断机制

4.1、lockInterruptibly()

ReentrantLock 中的 lockInterruptibly() 函数:由 aquireInterruptibly() 模板方法来实现

public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

4.2、acquireInterruptibly() 模板方法

acquireInterruptibly()

acquireInterruptibly() 模板方法对应的代码实现如下所示,代码实现也非常简单

  • 如果线程被中断,则抛出 InterruptedException 异常,否则调用 tryAcquire() 竞争获取锁
  • 如果获取锁成功,则直接返回,否则调用 doAcquireInterruptible() 函数
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
if (!tryAcquire(arg)) doAcquireInterruptibly(arg);
}

doAcquireInterruptibly()

doAcquireInterruptibly() 函数的代码实现如下所示,跟之前讲的 acquireQueued() 函数的代码实现非常相似,唯一的区别是对中断的响应处理不同
parkAndCheckInterrupt() 函数返回有两种情况,一种是其他线程调用了 unpark() 函数取消阻塞,另一种是被其他线程中断,对于第二种情况

  • acquireQueued() 函数不对中断做任何处理,继续等待锁
  • doAcquireInterruptibly() 函数则是将中断包裹为 InterruptedException 异常抛出,终止等待锁

因此调用 acquire() 实现的 lock() 函数,在阻塞等待锁时不会被中断,调用 acquireInterruptibly() 实现的 lockInterruptibly() 函数,在阻塞等待锁时可以被中断

private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (; ; ) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (parkAndCheckInterrupt()) throw new InterruptedException(); // 区别: 抛出异常!
}
} finally {
if (failed) cancelAcquire(node);
}
}

5、超时机制

5.1、tryLock()

ReentrantLock 中带超时时间的 tryLock() 函数:由 tryAquireNanos() 模板方法来实现

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

5.2、tryAquireNanos() 模板方法

tryAquireNanos()

tryAquireNanos() 模板方法的代码实现如下所示,代码实现也非常简单

  • 如果线程被中断,则直接抛出 InterruptedException 异常,否则调用 tryAcquire() 竞争获取锁
  • 如果获取锁成功,则直接返回,否则调用 doAcquireNanos() 函数
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

doAcquireNanos()

doAcquireNanos() 函数的代码实现如下所示,在 doAcquireInterruptibly() 函数的代码实现的基础之上,doAcquireNanos() 函数又添加了对超时的处理机制
因此使用 tryAcquireNanos() 实现的 ReentrantLock 的 tryLock() 函数,既支持中断,又支持设置超时时间

private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L) return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (; ; ) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L) return false; // 如果获取锁失败,最终在这里返回
// 不着急阻塞, 先自旋一下
if (nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); // 超时阻塞
if (Thread.interrupted()) throw new InterruptedException();
}
} finally {
if (failed) cancelAcquire(node);
}
}

parkNanos()

为了支持超时阻塞,在阻塞线程时,doAcquireNanos() 函数调用 parkNanos() 函数,parkNanos() 函数的实现方式跟 park() 函数差不多
在讲解 synchronized 的时候我们提到,park() 函数的代码实现大致如下所示
parkNanos() 只需要将其中的 pthread_cond_wait() 函数替换成了pthread_cond_timewait() 函数便可以实现超时等待

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
boolean ready = false;
void park() {
// ...
pthread_mutex_lock(&mutex);
while (!ready) {
pthread_cond_wait(&cond, &mutex);
}
ready = false;
pthread_mutex_unlock(&mutex);
// ...
}

6、课后思考题

本节中我们讲到 ReentrantLock 中

  • lock() 函数使用 AQS 中的 acquire() 模板方法来实现
  • unlock() 函数使用 AQS 中的 release() 模板方法来实现
  • lockInterruptibly() 函数使用 acquireInterruptibly() 模板方法来实现
  • 带超时时间的 tryLock() 函数使用 AQS 中的 tryAcquireNanos() 模板方法来实现

那么 ReentrantLock 中的 tryLock() 函数是如何实现的呢?
tryLock() 相较于 lock() 区别在于,当尝试加锁失败之后,线程并不会进入队列等待唤醒重新竞争获取锁

posted @   lidongdongdong~  阅读(63)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开