AQS框架详解(四)

el/2024/2/25 22:38:30

AQS概述

定义

AbstractQueuedSynchronizer 抽象队列同步器是一个框架,它维护两种队列,基于一个状态量 state 来实现同步器,实现该框架时,仅仅需要实现几种方法就可以自定义同步器

两种队列

  • 同步等待队列 CLH

    CLH队列是一种基于双向链表数据结构的队列【FIFO】,队列中线程被阻塞

CLH队列

  • 条件等待队列

    Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁

条件等待队列

入队出队等操作,由AQS来帮忙完成并维护,无需锁实现来做。

锁实现

  • Exclusive 独占式 —— 只有一个线程能执行

    • ReentrantLock
  • Share 共享式 —— 多个线程可以同时执行

    • Semaphore/CountDownLatch
  • 独占-共享式 —— 实现了独占和共享的四个方法

    • ReentrantReadWriteLock

实现自定义锁,需要实现以下方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。返回尝试获取后的资源数
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后 允许唤醒后续等待节点 返回true,否则返回false。

AQS源码解析

waitStatus

定义:

​ 节点状态标记。负值表示节点处于有效等待状态,而正值表示节点已被CANCELLED

源码中很多地方用>0、<0来判断节点的状态是否正常

取值:

含义
CANCELLED(1)当前节点已取消调度,线程等待超时或者被中断,在CLH中取消等待
SIGNAL(-1)后继节点处于等待状态,后继节点入队时,会将前继节点的状态更新为SIGNAL;前驱节点如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
CONDITION(-2)表示当前节点条件等待队列中,当其他线程调用了Condition.signal()方法,该节点会从条件等待队列转移到CLH同步等待队列中,等待获取同步锁
PROPAGATE(-3)共享模式下,前驱节点不仅会唤醒它的后继节点,同时也有可能唤醒后继节点的后继节点

独占模式的方法顶层入口 【非中断】

acquire(int)

定义:

​ 此方法是独占模式下,忽略中断的**线程获取共享资源的顶层入口**

  • 如果获得了独占锁,直接返回
  • 如果没有获得独占锁,则进入CLH队列

源码:

/*** 获取独占锁* ReentrantLock 实现了公平与非公平的两种实现*/
public final void acquire(int arg) {//尝试获取锁if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 独占模式selfInterrupt();
}

方法解析:

方法作用
tryAcquire()尝试获取锁,如果成功返回ture
addWaiter()该线程加入CLH队列的尾部,并标记为Node.EXCLUSIVE模式
acquireQueued()已经在CLH队列中的节点,准备阻塞等待获取锁,返回当前CLH队列第一个节点能否抢到锁,抢到返回true
selfInterrupt()获取资源后进行自我中断,让锁外部的逻辑代码也识别到中断信号,做出一定的响应
tryAcquire(int)

定义:

​ 此方法**尝试去获取独占资源**。如果获取成功,则直接返回true,否则直接返回false

源码:

/*** 尝试获取独占锁,可指定锁的获取数量* ReentrantLock 实现了公平与非公平的两种实现*/
protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
}

解释:

​ 此方法定义了一个接口,这个方法交由==自定义同步器去实现==

为什么不定义为abstract?

​ 因为

  • 独占模式下只需要实现tryAcquire-tryRelease

  • 共享模式下只需要实现tryAcquireShared-tryReleaseShared

如果定义为abstract,那么一个模式需要去实现另一个模式的接口,增加工作量

addWaiter(Node)

定义:

​ 当前线程抢锁失败,为 当前线程和给定模式 创建节点 和 节点入队

源码:

private Node addWaiter(Node mode) {// 以给定模式 mode有两种:EXCLUSIVE(独占)和SHARED(共享)创建节点Node node = new Node(Thread.currentThread(), mode);// 尝试快速直接入队 【无竞争入队时,快速入队】Node pred = tail;if (pred != null) {// 尝试CAS入队,若失败,说明有其他线程也在抢先入队,走enq(node) 【尾插法】node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 上一步失败或者队列为空,则通过enq入队enq(node);return node;
}
enq(Node)

定义:

​ 将node节点在 多线程环境下 正常CAS尾插法入队

源码:

private Node enq(final Node node) {// CAS"自旋",直到成功加入队尾for (;;) {Node t = tail;if (t == null) { // 队列为空,创建一个空的dummy节点作为head节点,并将tail也指向它if (compareAndSetHead(new Node()))tail = head;} else {// 正常流程,放入队尾 【尾插法】node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}
acquireQueued(Node, int)

定义:

​ tryAcquire()导致抢锁失败,addWaiter()进入CLH队列,接下来需要==进行阻塞,等待资源;而这个方法的作用就是对线程进行阻塞==。

源码:

final boolean acquireQueued(final Node node, int arg) {// 标记是否成功拿到资源boolean failed = true;try {// 标记等待过程中是否被中断过boolean interrupted = false;// CASfor (;;) {// 拿到前驱节点final Node p = node.predecessor();// 如果前驱是head,即该节点已成老二,那么便有资格去尝试获取资源//(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)if (p == head && tryAcquire(arg)) {// 拿到资源后,获取同步状态成功,将head指向该节点。setHead(node);// **setHead中node.prev已置为null,此处再将head.next置为null**,之前的head没有GCRoots,会被GCp.next = null;// 成功获取资源failed = false;// 返回阻塞过程中是否被中断过return interrupted;}/** 如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()* 如果不可中断的情况下被中断了,那么会从park()中醒过来【中断唤醒】,发现拿不到资源,从而继续进入park()等待*/if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())// 如果阻塞过程中被中断过,哪怕只有那么一次,就将interrupted标记为trueinterrupted = true;}} finally {// 如果阻塞过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消节点在队列中的等待if (failed) cancelAcquire(node);}
}
shouldParkAfterFailedAcquire(Node, Node)

定义:

​ 检查当前节点的线程是否应该被阻塞,检查前面的节点是否处于放弃CANCELLED状态

源码:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//拿到前驱节点的状态int ws = pred.waitStatus;// 当前节点的行为说明前驱节点拿完锁的时候,通知自己一下 => 意味着当前节点可以安全地休息parkif (ws == Node.SIGNAL)return true;if (ws > 0) {/** 如果前驱节点是CANCELLED,**一直往前找,直到找到最近一个正常等待【0/1】的状态,并排在它的后边。*** 注意:那些放弃的节点,由于当前节点插队,它们相当于形成一个无引用链,稍后就会被(GC回收)!*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);// 在这里,彻底没有GCRoot引用这个无效引用链pred.next = node;} else {//如果前驱节点正常【不为CANCELLED】,那就把前驱节点的状态设置成SIGNAL,告诉前驱节点拿完锁的时候,通知自己一下。compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}

解释:

​ 整个流程中,如果前驱节点的状态不是SIGNAL,那么自己就不能安心去休息,需要去找个安全的休息点,同时可以再尝试下看有没有机会轮到自己拿锁【去acquireQueued()找机会】。

即:休息机会只有在前驱节点状态为SIGNAL的时候,才可以进行 (阻塞)

parkAndCheckInterrupt()

定义:

​ 如果线程找到安全的休息点后,那么该方法就是让线程去休息,进入阻塞状态。

源码:

/*** 阻塞当前节点,返回当前Thread的中断状态* LockSupport.park 底层实现逻辑调用系统内核功能 pthread_mutex_lock 阻塞线程*/private final boolean parkAndCheckInterrupt() {LockSupport.park(this);// 阻塞// 在这等待唤醒,唤醒有两种途径:// 1)被unpark();2)被interrupt();return Thread.interrupted();// 返回是否被中断唤醒}
总结

回到acquireQueued(),总结下该函数的具体流程:

  1. 当前节点已经进入队尾,**检查状态,**寻找安全休息点
  2. 调用park()将自己阻塞,等待unpark()或者interrupt()唤醒
  3. 唤醒后,假如自己是head.next节点,则尝试获取锁tryAcquire()
  4. 如果抢到锁,head指向自己,并返回是否被中断过
  5. 没有抢到锁,继续流程1
总结

回到acquire(),总结下该函数的具体流程:

  1. 调用自定义同步器的tryAcquire()去获取资源,成功则返回
  2. 失败,addWaiter()将该线程加入CLH队列尾部,标记为EXCLUSIVE独占模式 【还未阻塞
  3. acquireQueued()使线程在CLH队列阻塞,有机会时(unpark()或中断)去尝试获取资源,获取资源后返回是否被中断过
  4. 如果线程在等待过程中被中断过,进行自我中断selfInterrupt()

acquire()方法总结

release(int)

定义:

​ 此方法是独占模式下,线程释放共享资源的顶层入口

  • 它会释放指定量的资源
  • 如果彻底释放了(即state=0),它会唤醒CLH队列里的其他线程来获取资源

源码:

public final boolean release(int arg) {if (tryRelease(arg)) {// 找到头节点Node h = head;// 状态为0表示它已经是dummy节点if (h != null && h.waitStatus != 0)unparkSuccessor(h);// 唤醒CLH队列里的下一个线程return true;}return false;
}

方法解析:

方法解释
tryRelease(int)尝试释放指定arg量的资源,返回值是否彻底释放完毕!
unparkSuccessor(Node)唤醒CLH队列中下一个线程
tryRelease(int)

定义:

​ 此方法尝试去释放指定量的资源,跟tryAcquire()一样,这个方法是需要独占模式的自定义同步器去实现的。正常来说,tryRelease()都会成功的,因为这是独占模式,该线程来释放资源,前提是它肯定已经拿到独占资源

注意它的返回值release()是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了! 【要注意完全!】

源码:

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

定义:

​ 此方法用于唤醒CLH队列中下一个线程

源码:

private void unparkSuccessor(Node node) {// 这里,node一般为当前线程所在的结点int ws = node.waitStatus;// 置零当前线程所在的结点状态,允许失败。if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 找到下一个需要唤醒的结点sNode s = node.next;/*** 若node的后继结点为空,或状态为CANCEL(已失效),则从后尾部往前遍历找到最前的一个处于正常阻塞状态的结点* 进行唤醒*/if (s == null || s.waitStatus > 0) {s = null;// 从后向前找for (Node t = tail; t != null && t != node; t = t.prev) // 从这里可以看出,<=0的结点,都是还有效的结点,跳过无效节点,继续循环if (t.waitStatus <= 0)s = t;}// 唤醒if (s != null)LockSupport.unpark(s.thread);
}

mark:尾部寻找前的没CANCELLD的节点,然后唤醒,它被唤醒后,走到shouldParkAfterFailedAcquire()调整,往前走到head.next下,形成无效引用链,给GC

流程:

  • 寻找节点s【s为head的后继节点】
  • 如果s不为空且不为CANCELLED,唤醒
  • 否则 从后往前找有效节点ws!=CANCELLED
  • 可以找到一个最前面、且有效的节点

为什么要从后往前找?

多线程并发场景下,addWaiter()入队操作和cancelAcquire()取消排队操作都会造成next链的不一致,而prev链是强一致的,所以这时从后往前找是最安全的。

// 在enq()方法中,【普通入队】
private Node enq(final Node node) {// CAS"自旋",直到成功加入队尾for (;;) {Node t = tail;if (t == null) { // ...} else {// 这里保证了prev的强一致性,因为node先将prev指向尾结点node.prev = t;// CAS将当前节点赋值给tailif (compareAndSetTail(t, node)) {// 此时,线程A被阻塞,如果线程B执行完成入队操作t.next = node;return t;}}}
}

线程A CAS后被阻塞

线程A修改prev后

线程B执行完后

线程B执行完后

此时按照线程C的角度,Node1.next为空!

所以产生了next断链,不可以从头往后遍历

总结

release()是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放(即state=0),它会唤醒等待队列里的其他线程来获取资源。

共享模式的方法顶层入口 【非中断】

acquireShared(int)

定义:

​ **共享模式下,**线程获取资源的顶层入口,获取==指定的资源==,获取成功直接返回,失败则进入 CLH 队列

源码:

/*** 请求获取共享锁*/
public final void acquireShared(int arg) {//返回值小于0,获取同步状态失败,排队;获取同步状态成功,直接返回。if (tryAcquireShared(arg) < 0)// 失败则通过doAcquireShared()进入等待队列doAcquireShared(arg);
}
tryAcquireShared(int)

定义:

​ 定义一个方法给自定义同步器去实现具体的获取同步资源的逻辑,一般是返回剩余的资源量

源码:

protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();
}
doAcquireShared(int)

定义:

tryAcquire()抢锁失败,此方法就是将将当前节点放入CLH队列addWaiter(),并且对线程进行阻塞

源码:

/*** 将当前线程放入*等待队列尾部*休息,等待唤醒!*/
private void doAcquireShared(int arg) {// 将当前线程加入CLH队列尾部final Node node = addWaiter(Node.SHARED);// 是否成功标志boolean failed = true;try {// 等待过程中是否被中断过的标志boolean interrupted = false;for (;;) {// 拿到当前节点的前驱节点final Node p = node.predecessor();if (p == head) {//如果当前线程是head的下一个节点,**因为head是拿到资源的线程**,此时node被唤醒,很可能是head用完资源来唤醒自己的,所以再尝试获取资源tryAcquireSharedint r = tryAcquireShared(arg);// 获取锁成功if (r >= 0) {// 这一步设置当前node为head节点// 并设置node.ws->Node.PROPAGATE,然后唤醒node.thread 【传播模式】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);}
}
setHeadAndPropagate(Node,int)

定义:

​ 此方法是在拿到锁的基础上,将head设置给自己,并唤醒下一个邻居线程。

源码:

private void setHeadAndPropagate(Node node, int propagate) {// 保存旧的head节点Node h = head;// 将head指向node节点setHead(node);// propagate>0 表示还有剩余量// waitStatus表明 下一个邻居线程可被唤醒if (propagate > 0 || h == null || h.waitStatus < 0) {Node s = node.next;// 如果node是最后一个节点 ——> s==null// 如果node的后继节点是共享节点 ——> s.isShared()if (s == null || s.isShared())doReleaseShared();}
}

​ 此方法在setHead()的基础上多了一步,就是自己苏醒的同时,如果条件符合(比如还有剩余资源),还会去唤醒后继结点,毕竟是共享模式!

总结

acquireShared()流程

方法解释
tryAcquireShared()尝试获取资源,成功则直接返回;
doAcquireShared()获取锁失败,进入等待队列并阻塞,直到unpark()/interrupt()唤醒

acquire()的流程大同小异,多了个**自己拿到资源后,还会去唤醒后继队友的操作(这才是共享嘛)**

releaseShared(int)

定义:

共享模式下,线程释放共享资源的顶层入口,释放**指定的资源**

​ 如果成功释放且允许唤醒等待线程,它会唤醒CLH队列里的其他线程来获取资源。

释放资源,唤醒后继

源码:

public final boolean releaseShared(int arg) {// 尝试释放资源if (tryReleaseShared(arg)) {// 释放成功,唤醒后继结点doReleaseShared();return true;}return false;
}
doReleaseShared()

定义:

​ 方法用于唤醒head的后继节点,具有传播性,连续唤醒。

源码:

/*** 把当前结点设置为SIGNAL或者PROPAGATE* 唤醒head.next(B节点),B节点唤醒后可以竞争锁,成功后head->B,然后又会唤醒B.next,一直重复直到共享节点都唤醒* head节点状态为SIGNAL,重置head.waitStatus->0,唤醒head节点线程,唤醒后线程去竞争共享锁* head节点状态为0,将head.waitStatus->Node.PROPAGATE传播状态,表示需要将状态向后继节点传播*/
private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;// 头结点是SIGNAL,表明后继节点可以唤醒if (ws == Node.SIGNAL) {/* 这里不直接设为Node.PROPAGATE,这里需要控制并发* 因为入口有两个 **setHeadAndPropagate()/release()** ,避免两次unpark()*/if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue; // 设置失败,重新循环/* head状态为SIGNAL,且成功设置为0之后,可以去唤醒后继节点* 此时head、head.next的线程都唤醒了* head.next会去竞争锁,成功后head会指向获取锁的节点,* **就是head发生了变化*** 看最底下一行代码可知,head发生变化后会重新循环,继续唤醒head的下一个节点* 【一直一直唤醒后继节点的逻辑】 * 唤醒头的后继->改头节点->继续唤醒新头的后继*/unparkSuccessor(h);}/** 如果本身头节点的waitStatus是出于重置状态(waitStatus==0)的,将其设置为“传播”状态。* 意味着需要将状态向后一个节点传播*/else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;}if (h == head) // 如果head变了,重新循环,唤醒新head的后继break;}
}

Condition

定义:

ConditionObject监视器方法(waitnotifynotifyAll)分解为不同的对象,将它们与Lock接口的实现结合起来,Lock锁替代SynchronizedCondition替代Object监视器方法的使用

类比:

不同点ConditionObject
等待方法await()wait()
唤醒方法signal()notify()
唤醒方法signalAll()notifyAll()
Lock接口的锁实现(ReentrantLockMonitor锁实现(Synchronized
等待队列Wait queue (AQS自带)WaitSet (Monitor自带)
同步阻塞队列CLH队列(AQS自带)EntryList(Monitor自带)

await()

定义:

​ 该方法用于阻塞当前线程并释放锁,等待条件signal()唤醒,与Object.wait()方法类似,由AQS框架实现

源码:

通过AQS框架的内部类ConditionObject对象实现的**await()**方法

// 通过AQS框架的内部类ConditionObject对象实现的await()方法
public final void await() throws InterruptedException {// 判断当前线程是否中断,中断则抛出异常if (Thread.interrupted())throw new InterruptedException();// 将当前线程加入条件等待队列中 (类似于waitSet)Node node = addConditionWaiter();// 当前线程释放锁int savedState = fullyRelease(node);int interruptMode = 0;// 先判断这个节点是否在同步阻塞CLH队列上,如果不在,就乖乖地在条件阻塞队列中等吧!阻塞当前线程!// 你在CLH上面,你就能出来了while (!isOnSyncQueue(node)) {LockSupport.park(this); // 阻塞if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}// 上面通过signal()方法来被唤醒,然后执行下面逻辑// 上面确保你在CLH队列中了,这个acquireQueued()方法就是让你在CLH队列中阻塞if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode); // 外抛中断异常 
}
addConditionWaiter()

定义:

​ 该方法用于将线程加入条件等待队列中,类似于底层Monitor的waitSet队列,该队列中的节点都是在等待signal()信号

源码:

private Node addConditionWaiter() {// 条件等待队列队尾节点Node t = lastWaiter;// 如果队尾节点是CANCELLED状态,将他们清除if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter; // 找到真正有效的队尾节点}// 当前线程构造节点,CONDITION条件模式Node node = new Node(Thread.currentThread(), Node.CONDITION);/* * 为什么这里不用CAS入队?* 因为我当前线程还没有释放锁,现在只是入条件队列逻辑,所以不需要CAS保证!*/// 如果队列为空,那么直接入队就好了if (t == null)firstWaiter = node;else// 入最后的尾节点t.nextWaiter = node;lastWaiter = node;return node;
}
fullyRelease()

定义:

​ 该方法用于释放锁,参数为上面入队的节点,就是当前线程的节点

源码:

final int fullyRelease(Node node) {boolean failed = true;try {int savedState = getState();// 由Lock接口的具体实现来实现,【ReentrantLock为例子】,// 将同步等待队列CLH的下一个节点唤醒。// 基于BlockingQueue来说,如果该节点发现,阻塞队列BlockingQueue又是满的,那么就会再走一次await()逻辑,将自己条件阻塞。这里说明CLH队列中,可能有生产者节点和消费者节点同时阻塞在这上面if (release(savedState)) {// 这里释放锁的逻辑,**表明了为什么await()方法需要在Lock锁的同步块里面**,类比wait()+synchronizedfailed = false;return savedState;} else {// 说明释放锁失败了,为什么会失败?因为当前线程没有拿到lock锁!throw new IllegalMonitorStateException();}} finally {if (failed)node.waitStatus = Node.CANCELLED;}
}

signal()

定义:

​ 该方法用于唤醒条件等待队列中的第一个节点first

源码:

public final void signal() {// 这里表明为什么signal()方法为什么要在同步块里面,如果当前线程没有占有锁,抛出异常。类比notify()if (!isHeldExclusively())// notify()+synchronized原理可能就是这样throw new IllegalMonitorStateException();// 条件等待队列头节点Node first = firstWaiter;if (first != null)doSignal(first);
}
doSignal()

定义:

​ 去唤醒条件等待队列的头节点,让他加入CLH同步阻塞队列!

源码:

private void doSignal(Node first) {do {// firstWaiter保存下一个要处理的节点if ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null;// 断开first节点指向下一个要处理的节点的指针first.nextWaiter = null;} // 当节点转换不成功,并且还有下一个要处理的节点时,给我继续循环!// 因为还没有成功地做到唤醒条件等待队列的节点while (!transferForSignal(first) &&(first = firstWaiter) != null);
}
transferForSignal()

定义:

​ 将条件等待队列的节点 转换为 同步等待队列的节点 CLH

final boolean transferForSignal(Node node) {// 如果当前处理的节点不能CAS,说明它的状态已经不是Node.CONDITION,有可能被CANCELLED,所以直接返回转换失败!不要该节点了if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))return false;// 当前节点进入CLH同步等待队列中,并返回它的前驱节点Node p = enq(node);// 前驱节点的信号量int ws = p.waitStatus;// 如果前驱节点为CANCELLED状态,则unpark当前线程,让他往前同步// 如果前驱节点为正常状态,尝试CAS告诉前驱节点,我可以SIGNAL,但失败了,就唤醒当前处理的节点的线程,让他往前同步if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))LockSupport.unpark(node.thread);// 转换成功!return true;
}

AQS框架方法简单介绍

AQS框架方法简略


http://www.ngui.cc/el/4893706.html

相关文章

AQS框架应用(五)

AQS框架应用&#xff08;五&#xff09; 定义&#xff1a; ​ 基于AQS框架&#xff0c;Doug Lea给出了很多内置的实现类 实现类&#xff1a; 可以看出&#xff0c;有多个工具类是基于AQS框架实现的 独占式同步信号量 ReentrantLock Semaphore 共享式同步信号量 CountDown…

类加载机制(一)

类加载 定义&#xff1a; ​ 当 用到某个类 时&#xff0c;首先需要通过 类加载器 把类加载到JVM上 组成&#xff1a; 引导类加载器 —— 加载lib目录扩展类加载器 —— 加载ext目录应用程序类加载器 —— 加载ClassPath目录 【用户写的】自定义加载器 —— 加载用户自定义…

JVM内存模型(二)

JVM内存模型 定义&#xff1a; ​ JVM内存模型&#xff0c;又称为运行时数据区。【区别Java内存模型】 紫色 —— 线程私有 浅黄色 —— 线程共享 虚拟机栈 定义&#xff1a; ​ 虚拟机栈是一个线程私有的、FILO的结构。 ​ 一个线程到来时&#xff0c;从虚拟机栈中&…

JVM内存分配机制详解(三)

JVM对象创建过程 1.类加载检查 流程&#xff1a; JVM在遇到一条new指令检查这个指令的参数是否在常量池中定位到一个类的符号引用 【方法区】并检查 符号引用 所代表的类是否被类加载器加载 包括 new关键字、对象克隆、对象序列化等等 **总的来说&#xff1a;**执行一个类…

Spring IOC (三)

Spring IOC &#xff08;三&#xff09; refresh() 后八个方法 refresh() 定义&#xff1a; ​ 这个方法中&#xff0c;内置了13个方法&#xff0c;做了很多事情 方法&#xff1a; 方法作用prepareRefresh()保存了容器的启动时间&#xff0c;启动标志等obtainFreshBeanFac…

Spring循环依赖(四)

循环依赖 什么是循环依赖&#xff1f; 依赖&#xff1a;在Spring中&#xff0c;A类将其他的类当作属性时&#xff0c;就造成了A依赖其他类 定义&#xff1a; ​ 现有A、B两个类&#xff0c;如果A类将B类作为属性&#xff08;并设置Autowired&#xff09;&#xff0c;B类也将…

想要学习Python?8年老Python程序员的自学书单曝光,入门必看

什么是Python&#xff1f; 它是一种类似C语言的人工智能便捷语言。它简单易学、并且开源免费&#xff0c;应用面广&#xff0c;所以&#xff0c;是自学门槛相对低的一种计算机语言&#xff0c;能为你的生活、工作提升效率。 为什么选择学习python的人越来越多&#xff1f; 全…

Python基础学习:都给我学起来,这样快速处理CSV、JSON和XML数据的方法太简便了

Python的卓越灵活性和易用性使其成为最受欢迎的编程语言之一&#xff0c;尤其是对于数据处理和机器学习方面来说&#xff0c;其强大的数据处理库和算法库使得python成为入门数据科学的首选语言。在日常使用中&#xff0c;CSV&#xff0c;JSON和XML三种数据格式占据主导地位。下…

Python 强大的信号库:必看的blinker 入门级教程,简单易懂

1 信号 信号是一种通知或者说通信的方式&#xff0c;信号分为发送方和接收方。发送方发送一种信号&#xff0c;接收方收到信号的进程会跳入信号处理函数&#xff0c;执行完后再跳回原来的位置继续执行。 常见的 Linux 中的信号&#xff0c;通过键盘输入 CtrlC&#xff0c;就是…

初学者别找了,Python Pandas快速入门教程,吐血总结全网最详细教程

Python 是开源的&#xff0c;它很棒&#xff0c;但是也无法避免开源的一些固有问题&#xff1a;很多包都在做&#xff08;或者在尝试做&#xff09;同样的事情。如果你是 Python 新手&#xff0c;那么你很难知道某个特定任务的最佳包是哪个&#xff0c;你需要有经验的人告诉你。…