【JAVA重要知识 | 第三篇】深入理解并暴打AQS原理、ReentrantLock锁

文章目录

  • 3.深入理解AQS、ReentrantLock
    • 3.1AQS
      • 3.1.1AQS简介
      • 3.1.2核心结构
        • (1)设计模型
        • (2)组成部分
        • (3)State关键字
      • 3.1.3实现的两类队列
        • (1)同步队列
          • ①CLH
          • ②Node
          • ③主要行为
        • img条件队列
      • 3.1.4总结
    • 3.2ReentrantLock
      • 3.2.1Synchronized和ReentrantLock
        • (1)性能上的比较
        • (2)获取公平锁
        • (3)综述
      • 3.2.2可重入功能的实现原理
      • 3.2.3非公平锁的实现原理
        • (1)加锁
        • (2)释放锁
      • 3.2.4公平锁的实现原理
      • 3.2.4tryLock原理
      • 3.2.5可中断的获取锁
      • 3.2.6可超时的获取锁
    • 3.2.7小结

3.深入理解AQS、ReentrantLock

在这里插入图片描述

3.1AQS

3.1.1AQS简介

AQS即队列同步器AbstractQueuedSynchronizer(后面简称AQS)是实现锁和有关同步器的一个基础框架。

在JDK5中,Doug Lea在并发包中加入了大量的同步工具,例如==重入锁(ReentrantLock)、读写锁(ReentrantReadWriteLock)、信号量(Semaphore)、CountDownLatch(倒计时锁)==等,都是基于AQS的。

其内部通过一个被标识为volatile的名为state的变量来控制多个线程之间的同步状态。多个线程之间可以通过AQS来独占式或共享式的抢占资源。

基于AQS,可以很方便的实现Java中不具备的功能。

例如,在锁这个问题上,Java中提供的是synchronized关键字,用这个关键字可以很方便的实现多个线程之间的同步。但这个关键字也有很多缺陷,比如:

  • 不支持超时的获取锁:一个线程一旦没有从synchronized上获取锁,就会卡在这里,没有机会逃脱。所以通常由synchronized造成的死锁是无解的。
  • 不可响应中断
  • 不能尝试获取锁。如果尝试获取时没获取到,立刻返回,synchronized不具备这一特性。

而ReentrantLock基于AQS将上述几点都做到了。

3.1.2核心结构

从AbstractQueuedSynchronizer的名字可以看出,AQS中一定是基于队列实现的(Queue)

在AQS内部,是通过链表实现的队列

链表的每个元素是其内部类Node的一个实现。然后AQS通过实例变量head指向队列的头,通过实例变量tail指向队列的尾。

img

其源码定义如下:

/*** Head of the wait queue, lazily initialized.  Except for* initialization, it is modified only via method setHead.  Note:* If head exists, its waitStatus is guaranteed not to be* CANCELLED.*/
private transient volatile Node head;/*** Tail of the wait queue, lazily initialized.  Modified only via* method enq to add new wait node.*/
private transient volatile Node tail;/*** The synchronization state.*/
private volatile int state;static final class Node {/** 标识为共享式 */static final Node SHARED = new Node();/** 标识为独占式 */static final Node EXCLUSIVE = null;/** 同步队列中等待的线程等待超时或被中断,需要从等待队列中取消等待,进入该状态的节点状态将不再变化 */static final int CANCELLED =  1;/** 当前节点的后继节点处于等待状态,且当前节点释放了同步状态,需要通过unpark唤醒后继节点,让其继续运行 */static final int SIGNAL    = -1;/** 当前节点等待在某一Condition上,当其他线程调用这个Conditino的signal方法后,该节点将从等待队列恢复到同步队列中,使其有机会获取同步状态 */static final int CONDITION = -2;/** 表示下一次共享式同步状态获取状态将无条件的传播下去 */static final int PROPAGATE = -3;/* 当前节点的等待状态,取值为上述几个常量之一,另外,值为0表示初始状态 */volatile int waitStatus;/* 前驱节点 */volatile Node prev;/* 后继节点 */volatile Node next;/* 等待获取同步状态的线程 */volatile Thread thread;/* 等待队列中的后继节点 */Node nextWaiter;// ...
}
(1)设计模型

img

(2)组成部分

AQS 主要由三部分组成

  1. state 同步状态
  2. Node 组成的 CLH 队列
  3. ConditionObject 条件变量(包含 Node 组成的条件单向队列)。

state 用 volatile 来修饰,保证了我们操作的可见性,所以任何线程通过 getState() 获得状态都是可以得到最新值,但是 setState() 无法保证原子性,因此 AQS 给我们提供了 compareAndSetState 方法利用底层 UnSafe 的 CAS 功能来实现原子性。

(3)State关键字

对于 AQS 来说,线程同步的关键是对 state 的操作,可以说获取、释放资源是否成功都是由 state 决定的,比如 state>0 代表可获取资源,否则无法获取,所以 state 的具体语义由实现者去定义,现有的 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 定义的 state 语义都不一样。

  • ReentrantLock 的 state 用来表示是否有锁资源,变量记录了锁的重入次数
  • ReentrantReadWriteLockstate 高 16 位代表读锁状态,低 16 位代表写锁状态
  • Semaphore 的 state 用来表示可用信号的个数
  • CountDownLatchstate 用来表示计数器的值

3.1.3实现的两类队列

  1. 同步队列:服务于线程阻塞等待获取资源
  2. 条件队列:服务于线程因某个条件不满足而进入等待状态。 条件队列中的线程实际上已经获取到了资源,但是没有能够继续执行下去的条件,所以被打入条件队列并释放持有的资源,以让渡其它线程执行,如果未来某个时刻条件得以满足,则该线程会被从条件队列转移到同步队列,继续参与竞争资源,以继续向下执行。
(1)同步队列
①CLH

同步队列是基于链表实现的双向队列,也是 CLH 锁的变种。CLH 锁是 AQS 队列同步器实现的基础。

以下图为CLH的构成

img

  1. CLH 锁是有由 Craig, Landin, and Hagersten 这三个人发明的锁,取了三个人名字的首字母,所以叫 CLH Lock
  2. CLH 锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。
  3. CLH 队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋
②Node

AQS 以内部类 Node 的形式定义了同步队列结点。这就是前文看到的第一个内部类。

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 int waitStatus;/** 前驱结点 */volatile Node prev;/** 后置结点 */volatile Node next;/** 持有的线程对象 */volatile Thread thread;/** 对于独占模式而言,指向下一个处于 CONDITION 等待状态的结点;对于共享模式而言,则为 SHARED 结点 */Node nextWaiter;// ... 省略方法定义
}

Node 在 CLH 的基础上进行了变种。

CLH 是单向队列,其主要特点是自旋检查前驱节点的 locked 状态

而 AQS 同步队列是 双向队列,每个节点也有状态 waitStatus,而其并不是一直对前驱节点的状态自旋判断,而是自旋一段时间后阻塞让出 cpu 时间片(上下文切换),等待前驱节点主动唤醒后继节点

waitStatus 有如下 5 中状态:

  • CANCELLED = 1 表示当前结点已取消调度。当超时或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
  • SIGNAL = -1 表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为 SIGNAL。
  • CONDITION = -2 表示结点等待在 Condition 上,当其他线程调用了 Condition 的 signal() 方法后,CONDITION 状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE = -3 共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
  • INITIAL = 0 新结点入队时的默认状态

从上面的代码中可以看出,位于 CLH 链表中的线程以 2 种模式在等待资源,即 SHARED 和 EXCLUSIVE,其中 SHARED 表示共享模式,而 EXCLUSIVE 表示独占模式。

共享模式与独占模式的主要区别在于,同一时刻独占模式只能有一个线程获取到资源,而共享模式在同一时刻可以有多个线程获取到资源。典型的场景就是读写锁,读操作可以有多个线程同时获取到读锁资源,而写操作同一时刻只能有一个线程获取到写锁资源,其它线程在尝试获取资源时都会被阻塞。

③主要行为

AQS 类成员变量 head 和 tail 字段分别指向同步队列的头结点和尾结点:

	 /*** Head of the wait queue, lazily initialized.  Except for* initialization, it is modified only via method setHead.  Note:* If head exists, its waitStatus is guaranteed not to be* CANCELLED.*/private transient volatile Node head;/*** Tail of the wait queue, lazily initialized.  Modified only via* method enq to add new wait node.*/private transient volatile Node tail;

其中 head 表示同步队列的头结点,而 tail 则表示同步队列的尾结点,具体组织形式如下图:

img

当调用 AQS 的 acquire 方法获取资源时,如果资源不足则当前线程会被封装成 Node 结点添加到同步队列的末端(入队),头结点 head 用于记录当前正在持有资源的线程结点,而 head 的后继结点就是下一个将要被调度的线程结点,当 release 方法被调用时,该结点上的线程将被唤醒(出队),继续获取资源。

同步队列的主要行为是 :入队、出队

  • 入队

获取资源失败的线程需要封装成 Node 节点,接着尾部入队,在 AQS 中提供 addWaiter 函数完成 Node 节点的创建与入队。添加节点的时候,如 CLH 队列已经存在,通过 CAS 快速将当前节点添加到队列尾部,如果添加失败或队列不存在,则初始化同步队列。

/*** Creates and enqueues node for current thread and given mode.** @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared* @return the new node*/
private Node addWaiter(Node mode) {Node node = new Node(mode);for (;;) {Node oldTail = tail;if (oldTail != null) {node.setPrevRelaxed(oldTail);if (compareAndSetTail(oldTail, node)) {oldTail.next = node;return node;}} else {initializeSyncQueue();}}
}

总结:线程获取锁失败,入队列,将新节点加到 tail 后面然后对 tail 进行 CAS 操作,将 tail 指针后移到新节点上

  • 出队

CLH 队列中的节点都是获取资源失败的线程节点,当持有资源的线程释放资源时,会将 head.next 指向的线程节点唤醒(CLH 队列的第二个节点),如果唤醒的线程节点获取资源成功,线程节点清空信息设置为头部节点(新哨兵节点),原头部节点出队(原哨兵节点)

 protected final boolean tryRelease(int releases) {int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 如果 state=0 了,就是可以释放锁了free = true; setExclusiveOwnerThread(null); // 将拿锁线程置为 null}setState(c); // 重置同步器的 statereturn free; // 返回是否成功释放}private void unparkSuccessor(Node node) {// node 节点是当前释放锁的节点,也是同步队列的头节点int ws = node.waitStatus;// 如果节点已经被取消了,把节点的状态置为初始化if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 拿出队二 sNode s = node.next;// s 为空,表示 node 的后一个节点为空// s.waitStatus 大于 0,代表 s 节点已经被取消了// 遇到以上这两种情况,就从队尾开始,向前遍历,找到第一个 waitStatus 字段不是被取消的if (s == null || s.waitStatus > 0) {s = null;// 结束条件是前置节点就是 head 了for (Node t = tail; t != null && t != node; t = t.prev)// t.waitStatus <= 0 说明 t 当前没有被取消,肯定还在等待被唤醒if (t.waitStatus <= 0)s = t;}// 唤醒以上代码找到的线程if (s != null)LockSupport.unpark(s.thread);
}

总结:出队列,锁释放唤醒 head 的后继节点,head 的后继节点从阻塞中醒来,开始抢锁,获取锁成功此时 head 指针向后移一个位置,原先 head 的后继节点成为新的 head

img(2)条件队列

一个 AQS 可以对应多个条件变量

img

ConditionObject 内部维护着一个单向条件队列,不同于 CLH 队列,条件队列只入队执行 await 的线程节点,并且加入条件队列的节点,不能在 CLH 队列, 条件队列出队的节点,会入队到 CLH 队列

当某个线程执行了 ConditionObject 的 await 函数,阻塞当前线程,线程会被封装成 Node 节点添加到条件队列的末端,其他线程执行 ConditionObject 的 signal 函数,会将条件队列头部线程节点转移到 CLH 队列参与竞争资源,具体流程如下图:

img

一个 Condition 对象就有一个单项的等待任务队列。在一个多线程任务中我们可以 new 出多个等待任务队列。比如我们 new 出来两个等待队列。

 private Lock lock = new ReentrantLock();private Condition FirstCond = lock.newCondition();private Condition SecondCond = lock.newCondition();

所以真正的 AQS 任务中一般是一个任务队列 N 个等待队列的,因此我们尽量调用 signal 而少用 signalAll,因为在指定的实例化等待队列中只有一个可以拿到锁的。

3.1.4总结

  1. 状态管理:AQS 内部维护了一个状态变量state,通过该状态变量来表示共享资源的状态,可以是独占模式也可以是共享模式
  2. CAS 操作:AQS 使用 CAS(Compare And Swap)操作来实现对状态变量的原子性修改,确保线程安全性。
  3. 线程阻塞和唤醒:当一个线程尝试获取锁或访问资源时,如果资源已被其他线程占用,则会将该线程阻塞并加入同步等待队列,直到资源可用时再唤醒线程。
  4. 双向链表:AQS 使用双向链表来管理等待线程,保持线程之间的先后顺序,即按照先进先出的原则进行访问。
  5. 模板方法设计模式:AQS 提供了模板方法,允许子类通过实现特定的方法来控制锁的获取和释放过程,从而实现不同类型的同步器。

3.2ReentrantLock

ReentrantLock,重入锁,是JDK5中添加在并发包下的一个高性能的工具。

顾名思义,ReentrantLock支持同一个线程在未释放锁的情况下重复获取锁

3.2.1Synchronized和ReentrantLock

(1)性能上的比较

首先,ReentrantLock的性能要优于synchronized。下面通过两段代码比价一下。 首先是synchronized:

PS:当存在大量线程竞争锁时,多数情况下ReentrantLock的性能优于synchronized。

因为在JDK6中对synchronized做了优化

锁竞争不激烈的时候,多数情况下锁会停留在偏向锁和轻量级锁阶段,这两个阶段性能是很好的。

当存在大量竞争时,可能会膨胀为重量级锁,性能下降,此时的ReentrantLock应该是优于synchronized的。

(2)获取公平锁

公平性是啥概念呢?如果是公平的获取锁,就是说多个线程之间获取锁的时候要排队,依次获取锁;如果是不公平的获取锁,就是说多个线程获取锁的时候一哄而上,谁抢到是谁的。

由于synchronized是基于monitor机制实现的,它只支持非公平锁

但ReentrantLock同时支持公平锁和非公平锁。

(3)综述

ReentrantLock还有一些其他synchronized不具备的特性,这里来总结一下。

img

3.2.2可重入功能的实现原理

ReentrantLock的实现基于队列同步器(AbstractQueuedSynchronizer)后面简称AQS

ReentrantLock的可重入功能基于AQS的同步状态:state

核心原理:当某一线程获取锁后,将state值+1,并记录下当前持有锁的线程

再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,将state值再+1,如果不是,阻塞线程。 当线程释放锁时,将state值-1

当state值减为0时,表示当前线程彻底释放了锁,然后将记录当前持有锁的线程的那个字段设置为null,并唤醒其他线程,使其重新竞争锁。

// acquires的值是1
final boolean nonfairTryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 获取state的值int c = getState();// 如果state的值等于0,表示当前没有线程持有锁// 尝试将state的值改为1,如果修改成功,则成功获取锁,并设置当前线程为持有锁的线程,返回trueif (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// state的值不等于0,表示已经有其他线程持有锁// 判断当前线程是否等于持有锁的线程,如果等于,将state的值+1,并设置到state上,获取锁成功,返回true// 如果不是当前线程,获取锁失败,返回falseelse if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

3.2.3非公平锁的实现原理

ReentrantLock有两个构造函数:

// 无参构造,默认使用非公平锁(NonfairSync)
public ReentrantLock() {sync = new NonfairSync();
}// 通过fair参数指定使用公平锁(FairSync)还是非公平锁(NonfairSync)
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

sync是ReentrantLock的成员变量,是其内部类Sync的实例。NonfairSync和FairSync都是Sync类的子类。可以参考如下类关系图:

img

Sync继承了AQS,所以他具备了AQS的功能。同样的,NonfairSync和FairSync都是AQS的子类。

当我们通过无参构造函数获取ReentrantLock实例后,默认用的就是非公平锁。

(1)加锁

下面将通过如下场景描述非公平锁的实现原理:假设一个线程(t1)获取到了锁,其他很多没获取到锁的线程(others_t)加入到了AQS的同步队列中等待,当这个线程执行完,释放锁后,其他线程重新非公平的竞争锁。

注意:新来的线程执行lock方法时,都要尝试下能不能直接获取锁,若获取锁成功,则记录当前线程,否则调用AQS的acqurire方法,进入到同步队列中等待

final void lock() {// 线程t1成功的将state的值从0改为1,表示获取锁成功// 并记录当前持有锁的线程if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// others_t线程们没有获取到锁acquire(1);
}

如果获取锁失败,会调用AQS的acquire方法

public final void acquire(int arg) {// tryAcquire是个模板方法,在NonfairSync中实现,如果在tryAcquire方法中依然获取锁失败,会将当前线程加入同步队列中等待(addWaiter)if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}

tryAcquire的实现如下,其实是调用了上面的nonfairTryAcquire方法

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}
(2)释放锁

OK,此时t1获取到了锁,others_t线程们都跑到同步队列里等着了

某一时刻,t1自己的任务执行完成,调用了释放锁的方法(unlock)。

public void unlock() {// 调用AQS的release方法释放资源sync.release(1);
}
public final boolean release(int arg) {// tryRelease也是模板方法,在Sync中实现if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)// 成功释放锁后,唤醒同步队列中的下一个节点,使之可以重新竞争锁// 注意此时不会唤醒队列第一个节点之后的节点,这些节点此时还是无法竞争锁unparkSuccessor(h);return true;}return false;
}
protected final boolean tryRelease(int releases) {// 将state的值-1,如果-1之后等于0,释放锁成功int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}

这时锁被释放了,唤醒的线程(一个)和新来的线程重新竞争锁不包含同步队列后面的那些线程)。

回到lock方法中,由于此时所有线程都能通过CAS来获取锁,并不能保证被唤醒的那个线程能竞争过新来的线程,所以是非公平的。这就是非公平锁的实现。

这个过程大概可以描述为下图这样子:

img

3.2.4公平锁的实现原理

公平锁与非公平锁的释放锁的逻辑是一样的,都是调用上述的unlock方法,最大区别在于获取锁的时候直接调用的AQS的acquire方法,没有先尝试获取锁

static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;// 获取锁,与非公平锁的不同的地方在于,这里直接调用的AQS的acquire方法,没有先尝试获取锁// acquire又调用了下面的tryAcquire方法,核心在于这个方法final void lock() {acquire(1);}/*** 这个方法和nonfairTryAcquire方法只有一点不同,在标注为#1的地方* 多了一个判断hasQueuedPredecessors,这个方法是判断当前AQS的同步队列中是否还有等待的线程* 如果有,返回true,否则返回false。* 由此可知,当队列中没有等待的线程时,当前线程才能尝试通过CAS的方式获取锁。* 否则就让这个线程去队列后面排队。*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// #1if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
}

通过注释可知,在公平锁的机制下,任何线程想要获取锁,都要排队,不可能出现插队的情况。这就是公平锁的实现原理。

这个过程大概可以描述为下图这样子:

img

3.2.4tryLock原理

tryLock做的事情很简单:让当前线程尝试获取一次锁,成功的话返回true,否则false

其实就是调用了nonfairTryAcquire方法来获取锁。

public boolean tryLock() {return sync.nonfairTryAcquire(1);
}

至于获取失败的话,他也不会将自己添加到同步队列中等待,直接返回false,让业务调用代码自己处理。

3.2.5可中断的获取锁

中断,也就是通过Thread的interrupt方法将某个线程中断,中断一个阻塞状态的线程,会抛出一个InterruptedException异常。

如果获取锁是可中断的,当一个线程长时间获取不到锁时,我们可以主动将其中断,可避免死锁的产生。

其实现方式如下:

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

会调用AQS的acquireInterruptibly方法:

public final void acquireInterruptibly(int arg)throws InterruptedException {// 判断当前线程是否已经中断,如果已中断,抛出InterruptedException异常if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);
}

此时会优先通过tryAcquire尝试获取锁,如果获取失败,会将自己加入到队列中等待,并可随时响应中断。

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 GCfailed = false;return;}// 获取锁失败,在parkAndCheckInterrupt方法中,通过LockSupport.park()阻塞当前线程,// 并调用Thread.interrupted()判断当前线程是否已经被中断// 如果被中断,直接抛出InterruptedException异常,退出锁的竞争队列if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())// #1throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}

PS:不可中断的方式下,代码#1位置不会抛出InterruptedException异常,只是简单的记录一下当前线程被中断了。

3.2.6可超时的获取锁

通过如下方法实现,timeout是超时时间,unit代表时间的单位(毫秒、秒…)

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

可以发现,这也是一个可以响应中断的方法。然后调用AQS的tryAcquireNanos方法:

public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);
}

doAcquireNanos方法与中断里面的方法大同小异,下面在注释中说明一下不同的地方:

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 GCfailed = false;return true;}// 计算到截止时间的剩余时间nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L) // 超时了,获取失败return false;// 超时时间大于1000纳秒时,才阻塞// 因为如果小于1000纳秒,基本可以认为超时了(系统调用的时间可能都比这个长)if (shouldParkAfterFailedAcquire(p, node) &&nanosTimeout > spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);// 响应中断if (Thread.interrupted())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}

3.2.7小结

本文首先对比了元老级的锁synchronized与ReentrantLock的不同,ReentrantLock具有一下优势: 同时支持公平锁与非公平锁 支持:尝试非阻塞的一次性获取锁 支持超时获取锁 支持可中断的获取锁 * 支持更多的等待条件(Condition)

然后介绍了几个主要特性的实现原理,这些都是基于AQS的。

  1. 首先,ReentrantLock 采用了独占模式,即同一时刻只允许一个线程持有锁。这保证了被锁保护的临界区只能被一个线程访问,从而避免了多个线程同时修改共享资源导致的数据竞争和不一致性。
  2. ReentrantLock的核心,是通过修改AQS中state的值来同步锁的状态。 通过这个方式,实现了可重入
  3. ReentrantLock具备公平锁和非公平锁,默认使用非公平锁。其实现原理主要依赖于AQS中的同步队列。

最后,可中断的机制是内部通过Thread.interrupted()判断当前线程是否已被中断,如果被中断就抛出InterruptedException异常来实现的。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/718559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中霖教育:注册安全工程师考是科目有哪些?

注册安全工程师的类型是职业资格证书&#xff0c;需要满足报名条件才能参加考试&#xff0c;考试通过就能发放证书。报名时间一般在八月份&#xff0c;考试时间在十月底左右。 考试科目&#xff1a; 《安全生产法律法规》 《安全生产管理》 《安全生产技术基础》 《安全生…

golang实现openssl自签名双向认证

第一步&#xff1a;生成CA、服务端、客户端证书 1. 生成CA根证书 生成CA证书私钥 openssl genrsa -out ca.key 4096创建ca.conf 文件 [ req ] default_bits 4096 distinguished_name req_distinguished_name[ req_distinguished_name ] countryName …

怎么使用curl2py自动构造爬虫代码并进行网络爬虫

目录 一、了解curl2py 二、安装curl2py 三、使用curl2py生成爬虫代码 四、实际案例&#xff1a;爬取网页数据 五、总结与建议 在当今数据驱动的时代&#xff0c;网络爬虫成为了获取数据的重要工具。对于初学者来说&#xff0c;手动编写爬虫代码可能是一项挑战。幸运的是&a…

PyTorch-神经网络

神经网络&#xff0c;这也是深度学习的基石&#xff0c;所谓的深度学习&#xff0c;也可以理解为很深层的神经网络。说起这里&#xff0c;有一个小段子&#xff0c;神经网络曾经被打入了冷宫&#xff0c;因为SVM派的崛起&#xff0c;SVM不了解的同学可以去google一下&#xff0…

JavaScript 基础学习笔记(五):函数、作用域、匿名函数

目录 一、函数 1.1 声明和调用 1.2 形参和实参 1.3 返回值 二、作用域 2.1 全局作用域 2.2 局部作用域 三、匿名函数 3.1 函数表达式 3.2 立即执行函数 一、函数 理解函数的封装特性&#xff0c;掌握函数的语法规则 1.1 声明和调用 函数可以把具有相同或相似逻辑的代…

NLP_文本张量表示方法(代码示例)

目标 了解什么是文本张量表示及其作用.文本张量表示的几种方法及其实现. 1 文本张量表示 将一段文本使用张量进行表示&#xff0c;其中一般将词汇为表示成向量&#xff0c;称作词向量&#xff0c;再由各个词向量按顺序组成矩阵形成文本表示. ["人生", "该&q…

Linux 实现打印彩色进度条

文章目录 预备知识一、理解回车换行二、认识行缓冲1、代码一、二&#xff08;回车换行理解&#xff09;2、代码三、四&#xff08;sleep函数和ffush函数理解&#xff09; 三、简单倒计时1. 倒计时代码2、效果展示 四、进度条1、效果展示2、进度条代码makefileProcessBar.hProce…

tomcat 反向代理 自建博客 修改状态页 等

一 自建博客 随后&#xff0c;拷贝到webapps下面 并且做软连接 随后重定向 并且下载 cat >/etc/yum.repos.d/mysql.repo <<EOF [mysql57-community] nameMySQL 5.7 Community Server baseurlhttp://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/ enabled1 g…

第十四届蓝桥杯大赛B组 JAVA 蜗牛 (递归剪枝)

题目描述&#xff1a; 这天&#xff0c;一只蜗牛来到了二维坐标系的原点。 在 x 轴上长有 n 根竹竿。它们平行于 y 轴&#xff0c;底部纵坐标为 0&#xff0c;横坐标分别为 x1, x2, …, xn。竹竿的高度均为无限高&#xff0c;宽度可忽略。蜗牛想要从原点走到第 n 个竹竿的底部也…

全域电商数据集成管理与采集|API接口的采集与管理

如今&#xff0c;全渠道零售已是大势所趋。企业电商经营的一大现状就是数据分散各处&#xff0c;比如有来自电商平台私域数据、品牌一方数据、公开的第三方行业数据与电商平台C端页面数据等等。如何集成全域数据日益成为企业数字化基建的难题。 当前电商数据集成的主流方案为人…

【基于Matlab GUI的语音降噪系统设计】

客户不要了&#xff0c;挂网上吧&#xff0c;有需要自行下载~ 赚点辛苦费 ** 功能实现: ** 1、导入音频文件/录入音频&#xff0c;能实现播放功能。 2、对导入/录入的音频信号进行时域和频域分析&#xff0c;并制图。 3、可在导入/录入的音频信号上加入噪声&#xff0c;并能够播…

Apache JMeter 5.6.3 安装

源码下载 curl -O https://dlcdn.apache.org//jmeter/source/apache-jmeter-5.6.3_src.zipJMeter 下载 curl -O https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.zipjmeter.properties 里 设置中文 windows系统上解压&#xff0c;双击jmeter.bat 启动 执行参…

架构设计方法(4A架构)-应用架构

1、应用架构&#xff08;AA&#xff09;&#xff1a;业务价值与产品之间的桥梁&#xff0c;是企业架构的一个子集 2、应用架构包含“应用系统模块、应用服务、应用系统集成”3个关键要素 3、收集AS-IS应用架构&#xff0c;描绘现状&#xff0c;并识别改进机会点 4、描述对新系统…

uniapp 安卓YYEVAPlayer MP4礼物播放器原生插件

插件介绍 安卓YYEVAPlayer MP4礼物播放器原生插件&#xff0c;是一个轻量的动画渲染库&#xff0c;使用Native Opengles 渲染视频&#xff0c;为你提供高性能、低开销的动画体验 对比传统的序列帧的动画播放方式&#xff0c;具有更高的压缩率&#xff0c;硬解码效率更高的优点…

【NR 定位】3GPP NR Positioning 5G定位标准解读(四)

目录 前言 6 Signalling protocols and interfaces 6.1 支持定位操作的网络接口 6.1.1 通用LCS控制平面架构 6.1.2 NR-Uu接口 6.1.3 LTE-Uu接口 6.1.4 NG-C接口 6.1.5 NL1接口 6.1.6 F1接口 6.1.7 NR PC5接口 6.2 终端协议 6.2.1 LTE定位协议&#xff08;LPP&#x…

TikTok企业认证教程:提升账号可信度的必备步骤

TikTok企业认证是TikTok平台用来验证账号真实性和权威性的方式。通过企业认证之后&#xff0c;企业能在TikTok上获得官方标识&#xff0c;可以增强品牌的专业形象&#xff0c;也有利于提升用户对企业内容的信任度。而且通过TikTok企业认证还可以解锁高级功能&#xff0c;如数据…

贪心(基础算法)--- 牛马耍杂技

耍杂技的牛 农民约翰的N头奶牛&#xff08;编号为1…N&#xff09;计划逃跑并加入马戏团&#xff0c;为此它们决定练习表演杂技。 奶牛们不是非常有创意&#xff0c;只提出了一个杂技表演&#xff1a; 叠罗汉&#xff0c;表演时&#xff0c;奶牛们站在彼此的身上&#xff0c…

【MATLAB】语音信号识别与处理:T1小波滤波算法去噪及谱相减算法呈现频谱

1 基本定义 T1小波滤波算法是一种基于小波变换的信号去噪算法。它可以有效地去除信号中的噪声&#xff0c;并保留信号的主要特征。该算法的主要思想是将信号分解为多个不同尺度的小波系数&#xff0c;然后通过对小波系数进行阈值处理来去除噪声。 具体来说&#xff0c;T1小波滤…

服务器数据恢复-服务器RAID5上层XFS文件系统分区数据恢复案例

服务器数据恢复环境&#xff1a; MD1200磁盘柜中的磁盘通过RAID卡创建了一组RAID5阵列&#xff0c;分配了一个LUN。在Linux操作系统层面对该LUN进行了分区&#xff0c;划分sdc1和sdc2两个分区&#xff0c;通过LVM扩容的方式将sdc1分区加入到了root_lv中&#xff1b;sdc2分区格式…

SSL证书验证失败怎么办?常见SSL证书验证失败原因及解决办法

网站与其访问者建立信任的主要方式就是通过签发SSL证书&#xff0c;因为SSL证书是由受信任的证书颁发机构&#xff08;CA&#xff09;在验证某个网站真实性和可信任性之后才颁发的。但是&#xff0c;网站部署SSL证书后&#xff0c;偶尔会出现SSL证书验证失败而导致错误&#xf…