Java之AbstractQueuedSynchronizer

要让你写一个java版的并发同步库,你会怎么思考设计???先思考三五分钟

 请先拜读下老外的paperhttp://gee.cs.oswego.edu/dl/papers/aqs.pdf

1.  简介

AbstractQueuedSynchronizer,简称AQS,中文翻译为抽象队列同步器,提供了基于同步状态、阻塞与唤醒线程及队列模型的基础框架。JDK中许多并发工具类的实现都基于AQS,如ReentrantLock、Semaphore、CountDownLatch等,它们都是基于AQS的抽象,实现不同的锁机制。单从类名来看,我们就已经可以得到3个重要信息:

  • Abstract:抽象类,通常无法直接使用;

  • Queued:队列,借助队列实现功能;

  • Synchronizer:同步器,用于控制并发。

2.  特性

先看下源码:

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {/*** 创建AQS实例,初始化state为0,为子类提供*/protected AbstractQueuedSynchronizer() { }/*----------------  同步队列构成 ---------------*/// 等待队列节点类型static final class Node {volatile int waitStatus;//线程在队列中的状态volatile Node prev;//链表前驱节点指针volatile Node next;//链表后继节点指针volatile Thread thread;//节点对应的线程对象Node nextWaiter;}/*** 除了初始化之外,它只能通过setHead方法进行修改。注意:如果head存在,它的waitStatus保证不会被取消*/private transient volatile Node head;/*** 等待队列的尾部,懒初始化,之后只在enq方法加入新节点时修改*/private transient volatile Node tail;/*----------------  同步状态相关 ---------------*//*** volatile修饰, 标识同步状态,state为0表示锁空闲,state>0表示锁被持有,可以大于1,表示被重入*/private volatile int state;/*** 利用CAS操作更新state值*/protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);}static final long spinForTimeoutThreshold = 1000L;// 这部分和CAS有关 // 获取Unsafe实例private static final Unsafe unsafe = Unsafe.getUnsafe();// 记录state在AQS类中的偏移值private static final long stateOffset;static {try {// 初始化state变量的偏移值stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));} catch (Exception ex) { throw new Error(ex); }}
}

2.1 同步队列

AQS通过同步双向队列来完成资源获取线程的排队工作,内部通过节点head【实际上是虚拟节点,真正的第一个线程在head.next的位置】和tail记录队首和队尾元素,队列元素类型为Node,同时每个节点还对应一个线程对象,我们可以把它理解为每个节点某一时段都代表一个线程。。

同步队列的结构如下,

  • 如果当前线程获取同步状态失败(锁)时,AQS 则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程

  • 当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

2.2  一个核心状态

volatile int state,其是一个整数类型,意为同步状态,也可以理解为资源。该字段的几个方法均为final,即禁止子类覆盖。

资源的状态state维护交由子类调用,由子类通过state判断是否获取锁,以及释放锁是否成功。state代表一种资源,具体的资源分配情况由具体的实现操作。state的更新是基于cas更新的,通过volatile关键词修饰,保障并发时的可见性和有序性。

小结:

类型名称含义

Node

head

队列的节点类型,等待(双向)队列的头节点

Node

tail

队列的节点类型,等待(双向)队列的尾节点

int

state

同步器状态

Thread

exclusiveOwnerThread

继承的变量,表示排它锁模式下线程的持有者

3.  数据结构

3.1 队列定义

3.1.1  双向队列

基于队列,普通链表是最先想到的实现方式,其定义如下:


// AQS静态内部类,作为链表/队列中的数据节点
static final class Node {// 表明是一个共享锁static final Node SHARED = new Node();// 表明是一个排它锁static final Node EXCLUSIVE = null;// 上面的节点状态,具体枚举定义见下表,通过cas更新volatile int waitStatus;// 链表前继节点volatile Node prev;// 链表后继节点volatile Node next;// 节点对应的线程对象volatile Thread thread;// 下一个等待节点,在条件队列中使用;同时也是用来区分排它锁节点和共享锁节点的标识Node nextWaiter;
}

其中节点状态waitStatus的枚举状态值如下:

变量名

变量值含义

CANCELLED

1

表明当前节点的线程已被取消

SIGNAL

-1

表明下一个节点需要前一节点唤醒,这样下一个节点便可以安心睡眠了

CONDITION

-2

表明线程在等待条件,条件队列才用的上,如ReentrantLock的Condition

PROPAGATE

-3

表明下一个共享节点应该被无条件传播,当需要唤醒下一个共享节点时,会一直传播唤醒下一个直到非共享节点

-0

初始值,刚竞争资源进入队列的时候的初始状态

 3.1.2 条件队列

// AQS内部类
public class ConditionObject implements Condition{// 条件队列第一个节点private transient Node firstWaiter;// 条件队列最后一个节点private transient Node lastWaiter;// 注意:上文提到Node结构中有一个nextWaiter节点,一个使用场景便是条件队列的下一个节点(看作单向链表结构)。// 当前线程等待,进入条件队列public final void await(){}public final long awaitNanos(long nanosTimeout){}// 唤醒基于当前条件等待的一个线程,从第一个开始,加入到同步队列中,等待获取锁资源public final void signal() {}// 唤醒所有条件等待线程,加入到同步队列中public final void signalAll() {}
}

ConditionObject中提到的await和signal开头的方法,类似于Object的wait()和notify() 方法,需要获取到锁后调用。

4. 重要方法分析

对于AQS来说,线程同步的关键是对state进行操作,根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。

4.1  独占式获取与释放同步状态

使用独占的方式获取的资源是与具体线程绑定的,如果一个线程获取到了资源,便标记这个线程已经获取到,其他线程再次尝试操作state获取资源时就会发现当前该资源不是自己持有的,就会在获取失败后阻塞。

// 独占式获取同步状态,成功后,其他线程需要等待该线程释放同步状态才能获取同步状态public final void acquire(int arg) {// 首先调用 tryAcquire【需要子类实现】尝试获取资源,本质就是设置state的值,获取成功就直接返回if (!tryAcquire(arg) &&// 获取失败,就将当前线程封装成类型为Node.EXCLUSIVE的Node节点,并插入AQS阻塞队列尾部// 然后通过自旋获取同步状态acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}// 与 acquire(int arg) 相同,但是该方法响应中断。// 如果其他线程调用了当前线程的interrupt()方法,响应中断,抛出异常。public final void acquireInterruptibly(int arg)throws InterruptedException {// interrupted()方法将会获取当前线程的中断标志并重置if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);}//尝试获取锁,如果获取失败会将当前线程挂起指定时间,时间到了之后当前线程被激活,如果还是没有获取到锁,就返回false。//另外,该方法会对中断进行的响应,如果其他线程调用了当前线程的interrupt()方法,响应中断,抛出异常。public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);}// 独占式释放同步状态public final boolean release(int arg) {// 尝试使用tryRelease释放资源,本质也是设置state的值if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)// LockSupport.unpark(thread) 激活AQS里面被阻塞的一个线程// 被激活的线程则使用tryAcquire 尝试,看当前状态变量state的值是否能满足自己的需要,//满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。unparkSuccessor(h);return true;}return false;}

4.2  共享式获取与释放同步状态

对应共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过CAS 方式竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用CAS 方式进行获取即可。

//共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,// 与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)// 尝试获取资源,如果成功则直接返回// 如果失败,则将当前线程封装为类型为Node.SHARED的Node节点并插入AQS阻塞队列尾部// 并使用LockSupport.park(this)挂起自己doAcquireShared(arg);}// 共享式获取同步状态,响应中断public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}//共享式获取同步状态,增加超时限制public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquireShared(arg) >= 0 ||doAcquireSharedNanos(arg, nanosTimeout);}//共享式释放同步状态public final boolean releaseShared(int arg) {// 尝试释放资源if (tryReleaseShared(arg)) {// 调用LockSupport.unpark(thread)激活AQS队列里被阻塞的一个线程。// 被激活的线程使用tryReleaseShared查看当前状态变量state是否能满足自己的需要。// 如果满足需要,则线程被激活继续向下运行,否则还是放入AQS队列并被挂起doReleaseShared();return true;}return false;}

5. AQS的实现与应用分析

AQS提供多种实现,用于不同的业务场景,下面我们一起来看一下常见的几种:

AQS实现

应用场景

ReentrantLock

可重入锁,对资源的互斥访问,支持多条件、超时、尝试获取等,如精准阻塞唤醒线程的生产消费模型的实现

ReentrantReadWriteLock

可重入读写锁,用于读写场景,如读多写少的业务场景,利用读读不互斥、读写互斥的特性实现高性能的数据一致性

CountDownLatch

计数器或闭锁,多个线程各持有一个资源,所有线程资源释放后唤醒最终等待的线程,起到线程间通讯的作用,如多线程分片计算最后统计的场景

Semaphore

信号灯、信号量,主要用于控制可以同时访问某种资源的线程个数,如做流量分流,对于公共资源有限的场景,以及数据库连接等

由于dubbo中大量使用ReentrantLock,

 

就以它分析

ReentrantLock意为可重入锁,是一种排它锁的实现,只能一个线程可访问,如果其它线程来竞争资源的话会进入同步队列进行等待,其包括公平与非公平两种方式。

对于这种对临界资源加锁互斥的实现,还有常见的JVM提供的synchronized,源码分析前,我们先对比下两者特性:

ReentrantLock

synchronized

实现机制

依赖于AQS

JVM实现基于对象的监视器锁

可重入性

可重入

可重入

灵活性

更加灵活,支持公平与非公平、超时尝试、多条件的锁等待和唤醒、可中断

相对没那么灵活

加/释放锁

显示调用api加锁,且需要显示释放,同时需要确保异常后也能释放

使用起来简单,不用显示的加锁和释放锁

使用场景

对锁的使用场景需要更加灵活,如可以通过多条件精准阻塞唤醒线程,如jdk本身提供的一些阻塞队列

本来是重量级锁,优化增加了偏向、轻量级锁等,在线程不怎么竞争的情况下或灵活度要求不那么高的场景下更推荐,如常见单例模式的DCL实现

ReentrantLock内部定义了一个继承AQS的类:

// ReentrantLock抽象静态内部类
abstract static class Sync extends AbstractQueuedSynchronizer {// 获取锁的抽象方法。为什么抽象往下看abstract void lock();// 非公平的获取锁final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// cas尝试,成功的话更新锁的持有线程if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 可重入锁的体现else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}// 关键:释放锁,该访问是实现了AQS的tryRelease方法的protected final boolean tryRelease(int releases){int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 如果释放后锁没有了,持有锁的线程标识也置为nullif (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}// 创建条件,对应AQS的条件队列final ConditionObject newCondition() {return new ConditionObject();}
}

Sync是个抽象类,包含公平锁FairSync非公平锁NonFairSync的两个实现:

// 非公平锁实现
static final class NonfairSync extends Sync {// 非公平获取锁,不用排队,来到就可以试一试尝试获取锁final void lock() {// 尝试修改AQS的state从0到1,成功的话表示获取同步锁成功,并设置当前锁持有线程if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// 否则调用AQS的竞争锁方法acquire(1);}protected final boolean tryAcquire(int acquires) {// 调用Sync的非同步锁尝试获取,实现见Syncreturn nonfairTryAcquire(acquires);}
}// 公平锁实现
static final class FairSync extends Sync {// 公平锁,没有尝试修改状态,直接获取锁final void lock() {// 内部会调用下面的tryAcquire(int acquires)方法acquire(1);}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 注意:hasQueuedPredecessors() 与非公平锁的区别的地方,对于公平锁,如果队列有节点,直接跳过尝试获取资源if (!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;}
}

ReentrantLock核心变量


// ReentrantLock的成员变量,核心的方法都在Sync类体现了
private final Sync sync;

ReentrantLock核心方法

返回值

方法名

说明
-

ReentrantLock()

构造器,默认为非公平锁;有参构造器可以指定使用公平锁

void

lock()

加锁

void

lockInterruptibly()

加锁,可中断,线程被中断会抛异常

boolean

tryLock()

尝试获取锁,不会进入AQS同步器队列,仅尝试cas state,成功与失败都会立马返回

void

unlock()

释放锁

Condition

newCondition()

获取条件对象

锁竞争源码分析

假设线程1持有锁执行任务时,线程2竞争锁资源。

// java.util.concurrent.locks.ReentrantLock.NonfairSyncfinal void lock() {// cas尝试获取资源,案例中线程1会获取成功,直接返回;// 线程2会进入else逻辑,竞争锁资源。这里主要看线程2的逻辑if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// 进入AQS竞争锁,继续往下看acquire(1);
}

接着,进入AQS类操作: 

// java.util.concurrent.locks.AbstractQueuedSynchronizerpublic final void acquire(int arg) {// 此处的tryAcquire(arg)会尝试cas资源,由AQS子类ReentrantLock实现的// 1、如果竞争成功则直接返回// 2、否则调用addWaiter(Node.EXCLUSIVE),创建一个排它锁节点// 3、再调用acquireQueued进入队列if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}// 继续看 addWaiter(Node.EXCLUSIVE) 创建排它锁节点
private Node addWaiter(Node mode) {// 创建节点,传入当前线程,表明线程与节点的对应,mode是排它锁的标识Node node = new Node(Thread.currentThread(), mode);Node pred = tail;// 如果尾节点不空if (pred != null) {// 将当前线程2所在节点的前继节点指向尾节点node.prev = pred;// cas将当前线程2所在节点设置成尾节点,成功的话则返回trueif (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 上面cas操作失败的情况下,会通过轮询不断尝试直至成功,并返回节点// 当前案例会进入当前方法,因为线程1虽然持有锁,但是没有队列,所以pred=null(tail也为null)enq(node);return node;
}// 不断轮询设置尾节点的操作
private Node enq(final Node node) {for (;;) { // 无限循环Node t = tail;if (t == null) { // 没有尾节点,cas一个新节点作为头节点,并且将尾节点也指向它// 注意:该节点没有对应的线程,可以看作是线程1的if (compareAndSetHead(new Node()))tail = head;} else {// 再一次循环到此,将线程2的节点设置成尾节点,直至成功。node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}// 继续看调用acquireQueued进入队列,已经通过 addWaiter(Node.EXCLUSIVE) 创建排它锁节点
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {// 中断标识,但如果中断也不会抛异常boolean interrupted = false;for (;;) { // 循环// 获取线程2节点的前继节点final Node p = node.predecessor();// 如果p节点是头节点,此例是的,所以会再次尝试获取下锁// 聪明啊,真是不放过没一次机会去尝试,如果刚巧线程1此刻执行完任务释放了锁,直接成功获取锁;当然主要应该为了唤醒后获取锁if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 线程1还没有执行完任务,所以会进入到这里,if中两个判断的方法源码见下文// parkAndCheckInterrupt()调用LockSupport.park(this);进入线程等待状态,等待唤醒if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())// 如果线程终端,设置interruptedinterrupted = true;}} finally {if (failed)// 如果中断/异常,会进行取消节点cancelAcquire(node);}
}// 继续看 shouldParkAfterFailedAcquire(p, node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取前继节点的等待状态,AQS介绍已经说过,默认是0,会经过下面的cas改成SIGNAL(-1)int ws = pred.waitStatus;if (ws == Node.SIGNAL)// 如果已经是-1,表示后继节点可以安心睡觉了,前节点锁释放后会唤醒后继节点return true;if (ws > 0) {// ws大于1表示节点状态已经取消了,可以跳过该节点了do {// 比较难看懂,从后往前看,pred = pred.prev表示前节点指到再前一个节点;// node.prev = pred当前node节点的前节点指向刚刚的pred。加上后面那句pred.next = node; // 其实就是删除中间的取消节点。node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 将waitStatue cas成signal状态compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}// 继续看,shouldParkAfterFailedAcquire调用后,最终会返回true,紧接着调用parkAndCheckInterrupt(),进入睡眠状态
private final boolean parkAndCheckInterrupt() {// 进入awit,正常唤醒LockSupport.unpark(线程)LockSupport.park(this); // 是否是中断返回return Thread.interrupted(); 
}

锁释放源码分析


// java.util.concurrent.locks.ReentrantLockpublic void unlock() {// 核心方法调用,进行锁释放sync.release(1); 
}

接着,进入AQS类操作:


// java.util.concurrent.locks.AbstractQueuedSynchronizerpublic final boolean release(int arg) {// 尝试释放锁,由AQS子类ReentrantLock实现if (tryRelease(arg)) { Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤醒h节点线程return true;}return false;
}

下面我们继续看ReentrantLock的tryRelease实现,可见释放锁的时候,对于公平和非公平锁,都是调用Sync类定义的方法


// java.util.concurrent.locks.ReentrantLock#Syncprotected final boolean tryRelease(int releases) {// 减后得到目前还被锁定的资源int c = getState() - releases;// 如果当前线程不是队列锁持有者,抛异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;// 如果没有锁了,队列锁持有者置空setExclusiveOwnerThread(null);}// 不用cas更新状态,会成功,因为当前线程是持有排它锁的setState(c);return free;
}

tryRelease()成功的话,会获取头节点,如果队列有节点(此例中的线程2),会继续调用AQS的unparkSuccessor(h)方法。


// java.util.concurrent.locks.AbstractQueuedSynchronizerprivate void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0)// 更新为0,进行复位compareAndSetWaitStatus(node, ws, 0);// 注意:获取头节点的下一个节点进行唤醒的,因为头节点是持有锁的节点。Node s = node.next;// s.waitStatus表示已经被取消了,会循环从后到前,找到第一个等待中的线程节点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)// 进行唤醒(此处,是唤醒了线程2的)LockSupport.unpark(s.thread);
}

好了,至此结束aqs的循序渐进介绍,从概念,特性,结构,应用,最后通过对ReentrantLock分析,相信已对AQS的同步机制有了更好的理解。AQS对同步状态和队列进行了定义和抽象,JDK本身基于此提供了如ReentrantLock、CountDownLatch、Semaphore等一系列的具体实现,让我们普通人可以更加方便的运用到日常的开发当中,当然框架dubbo和netty已经大量使用,比如ReentrantLock

参考:

  1. AbstractQueuedSynchronizer.java http://www.docjar.com/html/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.java.html

  2. Class AbstractQueuedSynchronizer  AbstractQueuedSynchronizer (Java Platform SE 8 )

  3.  从ReentrantLock的实现看AQS的原理及应用  从ReentrantLock的实现看AQS的原理及应用 - 美团技术团队
  4. Java源码学习:AbstractQueuedSynchronizer(AQS)学习 wetsion.site

  5. JUC同步器框架AbstractQueuedSynchronizer源码图文分析  https://www.throwable.club/2019/04/07/java-juc-aqs-source-code/

  6. 深度解析 Java 8:JDK1.8 AbstractQueuedSynchronizer 的实现分析(上)深度解析Java 8:JDK1.8 AbstractQueuedSynchronizer的实现分析(上)_语言 & 开发_刘锟洋_InfoQ精选文章

  7.  

    AbstractQueuedSynchronizer框架  https://t.hao0.me/java/2016/04/01/aqs.html

  8. AQS同步器以及各类lock锁的使用 http://jvm123.com/2020/04/aqs-lock-cas.html

  9. Detailed java lock queue synchronizer AQS  Detailed java lock queue synchronizer AQS

  10. java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject  java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject Java Exaples

  11.  

    图解JDK并发工具源码 - AbstractQueuedSynchronizer Sync Queue 图解JDK并发工具源码 - AbstractQueuedSynchronizer Sync Queue - Chris Blog - Java博文专集

  12.  源码分析JDK8之AbstractQueuedSynchronizer  java - 源码分析JDK8之AbstractQueuedSynchronizer - QueenKing - SegmentFault 思否
  13.  

    ReentrantLock.tryLock  http://shangdixinxi.com/detail-1475731.html

  14.  AbstractQueuedSynchronizer  AbstractQueuedSynchronizer – Rancho Wang
  15. Java多线程 20 - AbstractQueuedSynchronizer详解(1) https://blog.coderap.com/article/228

  16. Java多线程与并发基础  https://www.bbsmax.com/R/QW5YjPVGdm/

  17. Java并发编程实战: AQS 源码 史上最详尽图解+逐行注释 https://www.tuicool.com/articles/Yviaiqz

  18. ReentrantLock源码分析--jdk1.8  ReentrantLock源码分析--jdk1.8 - 编程语言 - 亿速云

  19.  java源码分析-abstractqueuedsynchronizer  https://medium.com/@wangwei09310931/java%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-abstractqueuedsynchronizer-1b1dda2139b7
  20. Java并发指南8:AQS中的公平锁与非公平锁,Condtion Java并发指南8:AQS中的公平锁与非公平锁,Condtion_ITPUB博客

  21.  

    Java 锁的那些事儿 Java锁的那些事儿_语言 & 开发_骆向南_InfoQ精选文章

  22. Java并发包中的AQS  https://blog.xieyangzhe.com/archives/836

  23.   java  AbstractQueuedSynchronizer  http://www.howsoftworks.net/java-AbstractQueuedSynchronizer

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

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

相关文章

Git 原理与使用

1.版本控制器 所谓的版本控制器&#xff0c;就是能让你了解到⼀个⽂件的历史&#xff0c;以及它的发展过程的系统。通俗的讲就是⼀个可以记录⼯程的每⼀次改动和版本迭代的⼀个管理系统&#xff0c;同时也⽅便多⼈协同作业。 ⽬前最主流的版本控制器就是 Git 。Git 可以控制电脑…

vue 简单实验 v-bind 变量与html属性绑定

1.代码 <script src"https://unpkg.com/vuenext" rel"external nofollow" ></script> <div id"bind-attribute"><span v-bind:title"message">鼠标悬停几秒钟查看此处动态绑定的提示信息&#xff01;</sp…

【附安装包】CIMCO Edit 8安装教程|数控编程必备

软件下载 软件&#xff1a;CIMCO Edit版本&#xff1a;8语言&#xff1a;简体中文大小&#xff1a;249.18M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baid…

学习JAVA打卡第四十一天

字符串与字符数组、字节数组 ⑴字符串与字符数组 String类的构造方法String&#xff08;char a[]&#xff09;和String&#xff08;char a[]&#xff09;,int offset,int length,分别用数组a中的全部字符和部分字符创建string对象。 String类也提供将string对象的字符序列存…

Docker常见配置实验

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 1、拉取mysql5.6与owncloud的镜像 docker pull mysql:5.6 docker pull mysql:5.6 2、生成容器实例&#xff0c;构建个人网盘 docker run -d --name mydb1 --env MYSQL_ROOT_PASSWORD123456 mysql:5.6 docker …

mac清理磁盘空间软件有哪些 mac清理磁盘空间怎么清理

随着时间的推移&#xff0c;Mac电脑上的文件会越来越多&#xff0c;很快就会占满磁盘空间。这时候&#xff0c;我们需要一个好的Mac清理磁盘空间软件来释放空间&#xff0c;保持电脑的良好性能。那么&#xff0c;mac清理磁盘空间软件有哪些呢&#xff1f;接下来&#xff0c;我将…

设计模式(单例模式,工厂模式),线程池

目录 什么是设计模式? 单例模式 饿汉模式 懒汉模式 工厂模式 线程池 线程池种类 ThreadPoolExcutor的构造方法: 手动实现一个线程池 什么是设计模式? 计算机行业程序员水平层次不齐,为了让所有人都能够写出规范的代码,于是就有了设计模式,针对一些典型的场景,给出一…

java+springboot+mysql村务档案管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的村务档案管理系统&#xff0c;系统包含超级管理员、工作人员角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;系统用户管理&#xff08;工作人员管理&#xff09;&#xff1b;公开资料&#xff1b;会议记录&…

.NET 最便捷的Log4Net日志记录器

最便捷的Log4Net使用方法 LOG4NET 配置日志记录器开始引用nuget LOG4NET 配置日志记录器 Apache log4net 库是一个帮助程序员将日志语句输出到各种的工具 的输出目标。log4net是优秀的Apachelog4j™框架的移植 Microsoft.NET 运行时。我们保持了与原始log4j相似的框架 同时利…

Rust处理JSON

基本操作 Cargo.toml: [package]name "json"version "0.1.0"edition "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]serde { version "1", features …

前端(十四)——DOM节点操作手册:你需要了解的一切

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;DOM节点操作手册&#xff1a;你需要了解的一切 文章目录 前言DOM基础知识操作现有节点创建新节点遍历节点树修改节点属性和样式事件处理实践应用动态创建表格动态更新列表 前言 DOM&#xff08;文档…

计算机网络第3章(数据链路层)

计算机网络第3章&#xff08;数据链路层&#xff09; 3.1 数据链路层概述3.1.1 概述3.1.2 数据链路层使用的信道3.1.3 三个重要问题 3.2 封装成帧3.2.1 介绍3.2.2 透明传输3.2.3 总结 3.3 差错检测3.3.1 介绍3.3.2 奇偶校验3.3.3 循环冗余校验CRC(Cyclic Redundancy Check)3.3.…

【FAQ】安防监控视频汇聚平台EasyCVR接入GB国标设备,无法显示通道信息的排查方法

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

【分析绘图】R语言实现一些常见的绘图

微生信-在线绘图网站 线性图 library(ggplot2)x <- rnorm(100, 14, 5) # rnorm(n, mean 0, sd 1) y <- x rnorm(100, 0, 1) ggplot(data NULL, aes(x x, y y)) # 开始绘图geom_point(color "darkred") # 添加点annotate("text",x 13,…

idea使用tomcat

1. 建立javaweb项目 2. /WEB-INF/web.xml项目配置文件 如果javaweb项目 先建立项目&#xff0c;然后在项目上添加框架支持&#xff0c;选择javaee 3. 项目结构 4.执行测试&#xff1a;

系统稳定性与高可用保障

一、前言 高并发、高可用、高性能被称为互联网三高架构&#xff0c;这三者都是工程师和架构师在系统架构设计中必须考虑的因素之一。今天我们就来聊一聊三 H 中的高可用&#xff0c;也是我们常说的系统稳定性。 > 本篇文章只聊思路&#xff0c;没有太多的深入细节。阅读全…

Springboot实现ENC加密

Springboot实现ENC加密 1、导入依赖2、配置加密秘钥&#xff08;盐&#xff09;3、获取并配置密文4、重启项目测试5、自定义前缀、后缀6、自定义加密方式 1、导入依赖 关于版本&#xff0c;需要根据spring-boot版本&#xff0c;自行修改 <dependency><groupId>co…

Python可视化工具库实战

Matplotlib Matplotlib 是 Python 的可视化基础库&#xff0c;作图风格和 MATLAB 类似&#xff0c;所以称为 Matplotlib。一般学习 Python 数据可视化&#xff0c;都会从 Matplotlib 入手&#xff0c;然后再学习其他的 Python 可视化库。 Seaborn Seaborn 是一个基于 Matplo…

微服务 Nacos配置热部署

在nacos中添加配置文件 在配置列表中添加配置&#xff0c; 注意&#xff1a;项目的核心配置&#xff0c;需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。 从微服务拉取配置 微服务要拉取nacos中管理的配置&#xff0c;并且与…

数据生成 | MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成

数据生成 | MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成 目录 数据生成 | MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成生成效果基本描述模型描述程序设计参考资料 生成效果 基本描述 1.MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成&#xff1b; 2.马尔科夫链蒙特卡洛方…