现在网站建设用什么语言,注册安全工程师是干什么的,wordpress获取用户id,seo网络推广案例一、AQS 在 Lock 中#xff0c;用到了一个同步队列 AQS#xff0c;全称 AbstractQueuedSynchronizer#xff0c;它是一个同步工具#xff0c;也是 Lock 用来实现线程同步的核心组件。
1.AQS 的两种功能
独占和共享。
独占锁#xff1a;每次只能有一个线程持有锁#x…一、AQS 在 Lock 中用到了一个同步队列 AQS全称 AbstractQueuedSynchronizer它是一个同步工具也是 Lock 用来实现线程同步的核心组件。
1.AQS 的两种功能
独占和共享。
独占锁每次只能有一个线程持有锁ReentrantLock 就是以独占方式实现的互斥锁。共享锁 允许多个线程同时获取锁 并发访问共享资源 比如ReentrantReadWriteLock
2.AQS的实现 AQS 队列内部维护的是一个 FIFO 的双向链表这种结构的特点是每个数据结构都有两个指针分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。 每个 Node 其实是由线程封装当线程争抢锁失败后会封装成 Node 加入到 AQS 队列中去 当获取锁的线程释放锁以后会从队列中唤醒一个阻塞的节点(线程)。
Node组成
static final class Node {// 排他锁的标识static final Node EXCLUSIVE null;// 如果带有这个标识证明是失效了static final int CANCELLED 1;// 具有这个标识说明后继节点需要被唤醒static final int SIGNAL -1;// Node对象存储标识的地方volatile int waitStatus;// 指向上一个节点volatile Node prev;// 指向下一个节点volatile Node next;// 当前Node绑定的线程volatile Thread thread;// 存储在Condition队列中的后继节点Node nextWaiter;// 返回前驱节点如果前驱节点为null抛出NPEfinal Node predecessor() throws NullPointerException {Node p prev;if (p null)throw new NullPointerException();elsereturn p;}// 将线程构造成一个Node添加到等待队列Node(Thread thread, Node mode) { // Used by addWaiterthis.nextWaiter mode;this.thread thread;}// 在Condition队列中使用Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus waitStatus;this.thread thread;}
}3.AQS添加线程 新的线程封装成 Node 节点追加到同步队列中设置 prev 节点以及修改当前节点的前置节点的 next 节点指向自己 通过 CAS 将 tail 重新指向新的尾部节点
4.AQS释放锁 head 节点表示获取锁成功的节点当头结点在释放同步状态时会唤醒后继节点如果后继节点获得锁成功会把自己设置为头结点 修改 head 节点指向下一个获得锁的节点 新的获得锁的节点将 prev 的指针指向 null
设置 head 节点不需要用 CAS原因是设置 head 节点是由获得锁的线程来完成的而同步锁只能由一个线程获得所以不需要 CAS 保证只需要把 head 节点设置为原首节点的后继节点并且断开原 head 节点的 next 引用即可。
二、CAS
1.CAS 的实现原理
protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}通过 cas 乐观锁的方式来做比较并替换如果当前内存中的state 的值和预期值 expect 相等则替换为 update。更新成功返回 true否则返回 false。这个操作是原子的不会出现线程安全问题。
2.state属性
private volatile int state; state 是 AQS 中的一个属性它在不同的实现中所表达的含义不一样 对于重入 锁的实现来说表示一个同步状态。它有两个含义的表示
当 state0 时表示无锁状态当 state0 时表示已经有线程获得了锁也就是 state1但是因为ReentrantLock 允许重入所以同一个线程多次获得同步锁的时候 state 会递增比如重入 5 次那么 state5。 而在释放锁的时候同样需要释放 5 次直到 state0其他线程才有资格获得锁
3.Unsafe类
Unsafe 类是在 sun.misc 包下不属于 Java 标准。但是很多 Java 的基础类库,包括一些被广泛使用的高性能开发库都是基于 Unsafe 类开发的比如 Netty、 Hadoop、 Kafka 等
Unsafe 可认为是 Java 中留下的后门提供了一些低层次操作如直接内存访问、线程的挂起和恢复、 CAS、线程同步、内存屏障
而 CAS 就是 Unsafe 类中提供的一个原子操作 public final native boolean compareAndSwapInt(Object obj, long stateOffset, int expect, int update);
obj需要改变的对象stateOffset偏移量(即之前求出来的 headOffset 的值)expect期待的值update更新后的值
整个方法的作用是如果当前时刻的值等于预期值 expect 相等则更新为新的期望值 update如果更新成功则返回 true否则返回false
stateOffset 一个 Java 对象可以看成是一段内存每个字段都得按照一定的顺序放在这段内存里通过这个方法可以准确地告诉你某个字段相对于对象的起始内存地址的字节偏移。用于在后面的 compareAndSwapInt 中去根据偏移量找到对象在内存中的具体位置 所以 stateOffset 表示 state 这个字段在 AQS 类的内存中相对于该类首地址的偏移量
compareAndSwapInt unsafe.cpp 文件中compareAndSwarpInt 的实现
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset,jint e, jint x))
UnsafeWrapper(Unsafe_CompareAndSwapInt);
oop p JNIHandles::resolve(obj); //将 Java 对象解析成 JVM 的 oop普通对象指针
jint* addr (jint *) index_oop_from_field_offset_long(p, offset); //根据对象 p 和地址偏移量找到地址
return (jint)(Atomic::cmpxchg(x, addr, e)) e; //基于 cas 比较并替换 x 表示需要更新的值 addr 表示 state在内存中的地址 e 表示预期值
UNSAFE_END三、ReentrantLock
ReentrantLock时序图
#mermaid-svg-mfdG1g79Z97HmJpQ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ .error-icon{fill:#552222;}#mermaid-svg-mfdG1g79Z97HmJpQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mfdG1g79Z97HmJpQ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-mfdG1g79Z97HmJpQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mfdG1g79Z97HmJpQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mfdG1g79Z97HmJpQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mfdG1g79Z97HmJpQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mfdG1g79Z97HmJpQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mfdG1g79Z97HmJpQ .marker.cross{stroke:#333333;}#mermaid-svg-mfdG1g79Z97HmJpQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mfdG1g79Z97HmJpQ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mfdG1g79Z97HmJpQ text.actortspan{fill:black;stroke:none;}#mermaid-svg-mfdG1g79Z97HmJpQ .actor-line{stroke:grey;}#mermaid-svg-mfdG1g79Z97HmJpQ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ .sequenceNumber{fill:white;}#mermaid-svg-mfdG1g79Z97HmJpQ #sequencenumber{fill:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ .messageText{fill:#333;stroke:#333;}#mermaid-svg-mfdG1g79Z97HmJpQ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mfdG1g79Z97HmJpQ .labelText,#mermaid-svg-mfdG1g79Z97HmJpQ .labelTexttspan{fill:black;stroke:none;}#mermaid-svg-mfdG1g79Z97HmJpQ .loopText,#mermaid-svg-mfdG1g79Z97HmJpQ .loopTexttspan{fill:black;stroke:none;}#mermaid-svg-mfdG1g79Z97HmJpQ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mfdG1g79Z97HmJpQ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-mfdG1g79Z97HmJpQ .noteText,#mermaid-svg-mfdG1g79Z97HmJpQ .noteTexttspan{fill:black;stroke:none;}#mermaid-svg-mfdG1g79Z97HmJpQ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mfdG1g79Z97HmJpQ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mfdG1g79Z97HmJpQ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mfdG1g79Z97HmJpQ .actorPopupMenu{position:absolute;}#mermaid-svg-mfdG1g79Z97HmJpQ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-mfdG1g79Z97HmJpQ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mfdG1g79Z97HmJpQ .actor-man circle,#mermaid-svg-mfdG1g79Z97HmJpQ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-mfdG1g79Z97HmJpQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}ReentrantLockNonfairSyncSyncAbstractQueuedSynchronizerlock()lock()acquire()tryAcquire()nonfairTryAcquire()true/falseaddWaiter()ReentrantLockNonfairSyncSyncAbstractQueuedSynchronizer1.lock()
public void lock() {// sync分为了公平和非公平sync.lock();
}sync是一个抽象的静态内部类它继承了 AQS 来实现重入锁的逻辑。 AQS 是一个同步队列它能够实现线程的阻塞以及唤醒 但它并不具备业务功能 所以在不同的同步场景中会继承 AQS 来实现对应场景的功能。
Sync 有两个具体的实现类
NofairSync表示可以存在抢占锁的功能也就是说不管当前队列上是否存在其他线程等待新线程都有机会抢占锁FailSync: 表示所有线程严格按照 FIFO 来获取锁
NonfairSync#lock()
final void lock() {// 通过CAS的方式尝试将state从0修改为1如果返回true代表修改成功如果修改失败返回falseif (compareAndSetState(0, 1))// 将一个属性设置为当前线程这个属性是AQS的父类提供的setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);
}非公平锁和公平锁最大的区别在于非公平锁中抢占锁的逻辑是不管有没有线程排队直接cas去抢占CAS 成功就表示成功获得了锁CAS 失败调用 acquire(1)走锁竞争逻辑
2.AQS#acquire()
acquire 是 AQS 中的方法如果 CAS 操作未能成功说明 state 已经不为 0此时 acquire(1)操作
public final void acquire(int arg) {// tryAcquire再次尝试获取锁资源如果尝试成功返回trueif (!tryAcquire(arg) // 获取锁资源失败后需要将当前线程封装成一个Node追加到AQS的队列中acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 线程中断selfInterrupt();
}通过 tryAcquire 尝试获取独占锁如果成功返回 true失败返回 false如果 tryAcquire 失败则会通过 addWaiter 方法将当前线程封装成 Node 添加到 AQS 队列尾部acquireQueued将 Node 作为参数通过自旋去尝试获取锁
3.NonfairSync#tryAcquire()
方法的作用是尝试获取锁如果成功返回 true不成功返回 false。 它是重写 AQS 类中的 tryAcquire 方法。
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}final boolean nonfairTryAcquire(int acquires) {// 获取当前线程final Thread current Thread.currentThread();// 获取AQS的state的值int c getState();// 如果state为0表示无锁状态尝试再次获取锁资源if (c 0) {// CAS尝试修改state从0-1如果成功设置ExclusiveOwnerThread属性为当前线程if (compareAndSetState(0, acquires)) {// 保存当前获得锁的线程,下次再来的时候不要再尝试竞争锁setExclusiveOwnerThread(current);return true;}}// 当前占有锁资源的线程是否是当前线程else if (current getExclusiveOwnerThread()) {// 将state 1int nextc c acquires;// 如果加1后小于0超所锁可重入的最大值抛出Errorif (nextc 0) // overflowthrow new Error(Maximum lock count exceeded);// 没问题就重新对state进行复制setState(nextc);// 锁重入成功return true;}return false;
}4.AQS#addWaiter()
当 tryAcquire 方法获取锁失败以后则会先调用 addWaiter 将当前线程封装成Node. 入参 mode 表示当前节点的状态传递的参数是 Node.EXCLUSIVE表示独占状态。意味着重入锁用到了 AQS 的独占锁功能
1.将当前线程封装成 Node2.当前链表中的 tail 节点是否为空如果不为空则通过 cas 操作把当前线程的node 添加到 AQS 队列3.如果为空或者 cas 失败调用 enq 将节点添加到 AQS 队列
// 说明前面获取锁资源失败放到队列中等待
private Node addWaiter(Node mode) {// 创建Node类并且设置thread为当前线程设置为排它锁Node node new Node(Thread.currentThread(), mode);// 获取AQS中队列的尾部节点默认是 nullNode pred tail;// 如果tail ! null说明队列中存在节点if (pred ! null) {// 把当前线程的 Node 的 prev 指向 tailnode.prev pred;// 通过 cas 把 node加入到 AQS 队列也就是设置为 tailif (compareAndSetTail(pred, node)) {// 设置成功以后把原 tail 节点的 next指向当前 nodepred.next node;return node;}}// tailnull,把 node 添加到同步队列enq(node);return node;
}// enq 通过自旋操作把当前节点加入到队列中
// 队列没有节点我是第一个 如果前面CAS失败也会进到这个位置重新往队尾进入。
private Node enq(final Node node) {// 死循环for (;;) {// 重新获取当前的tail节点为tNode t tail;if (t null) { // 队列没有节点, 我是第一个没头没尾都是空if (compareAndSetHead(new Node())) // 初始化一个Node作为head而这个head没有意义。// 将头尾都指向了这个初始化的Nodetail head;} else {// 有节点往队尾入// 当前节点的上一个指向tailnode.prev t;// 基于CAS的方式将tail节点设置为当前节点if (compareAndSetTail(t, node)) { // 将之前的为节点的next设置为当前节点t.next node;return t;}}}
}5.AQS#acquireQueued()
通过 addWaiter 方法把线程添加到链表后 会接着把 Node 作为参数传递给acquireQueued 方法去竞争锁
1.获取当前节点的 prev 节点2.如果 prev 节点为 head 节点那么它就有资格去争抢锁调用 tryAcquire 抢占锁3.抢占锁成功以后把获得锁的节点设置为 head并且移除原来的初始化 head节点4.如果获得锁失败则根据 waitStatus 决定是否需要挂起线程5.通过 cancelAcquire 取消获得锁的操作
// 已经将node加入到了双向队列中然后执行当前方法
final boolean acquireQueued(final Node node, int arg) {// 标识boolean failed true;try {// 标识boolean interrupted false;for (;;) {// 获取当前节点的上一个节点pfinal Node p node.predecessor();// 如果p是头说明有资格去争抢锁尝试获取锁资源state从0-1锁重入操作成功返回true失败返回falseif (p head tryAcquire(arg)) {// 获取锁成功设置head节点为当前节点将threadprev设置为null因为拿到锁资源了 setHead(node);p.next null; // 把原 head 节点从链表中移除帮助GC回收failed false; // 将标识修改为falsereturn interrupted; // 返回interrupted }// 保证上一个节点是-1才会返回true才会将线程阻塞等待唤醒获取锁资源if (shouldParkAfterFailedAcquire(p, node) // 基于Unsafe类的park方法挂起线程parkAndCheckInterrupt(); // 针对fail属性这里是唯一可能出现异常的地方JVM内部出现问题时可以这么理解fianlly代码块中的内容执行的几率约等于0interrupted true; // 返回当前线程在等待过程中有没有中断过}} finally {if (failed)cancelAcquire(node);}
}shouldParkAfterFailedAcquire 如果 ThreadA 的锁还没有释放的情况下 ThreadB 和 ThreadC 来争抢锁肯定是会失败那么失败以后会调用 shouldParkAfterFailedAcquire 方法 Node 有 5 中状态
CANCELLED1 在同步队列中等待的线程等待超时或被中断需要从同步队列中取消该 Node 的结点, 其结点的 waitStatus 为 CANCELLED即结束状态进入该状态后的结点将不会再变化SIGNAL-1 只要前置节点释放锁就会通知标识为 SIGNAL 状态的后续节点的线程CONDITION-2PROPAGATE(-3)享模式下 PROPAGATE 状态的线程处于可运行状态默认状态0 通过 Node 的状态来判断 ThreadA 竞争锁失败以后是否应该被挂起。
如果 ThreadA 的 pred 节点状态为 SIGNAL那就表示可以放心挂起当前线程通过循环扫描链表把 CANCELLED 状态的节点移除修改 pred 节点的状态为 SIGNAL,返回 false. 返回 false 时也就是不需要挂起返回 true则需要调用parkAndCheckInterrupt 挂起当前线程
// node是当前节点pred是上一个节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取上一个节点的状态int ws pred.waitStatus;// 如果上一个节点状态为SIGNAL意味着只需要等待其他前置节点的线程被释放if (ws Node.SIGNAL)return true; // 返回 true意味着可以直接放心的挂起了// ws 大于 0意味着 prev 节点取消了排队直接移除这个节点if (ws 0) {do {// 将当前节点的prev指针指向了上一个的上一个node.prev pred pred.prev;} while (pred.waitStatus 0); // 一直找到小于等于0的,从双向列表中移除 CANCELLED 的节点// 将重新标识好的最近的有效节点的nextpred.next node;} else {// 小于等于0不等于-1将上一个有效节点状态修改为-1compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}parkAndCheckInterrupt:
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
}使用 LockSupport.park 挂起当前线程变成 WATING 状态 Thread.interrupted返回当前线程是否被其他线程触发过中断请求也就是thread.interrupt(); 如果有触发过中断请求那么这个方法会返回当前的中断标识true并且对中断标识进行复位标识已经响应过了中断请求。 如果返回 true意味着在 acquire 方法中会执行 selfInterrupt()。
selfInterrupt:
static void selfInterrupt() {Thread.currentThread().interrupt();
}标识如果当前线程在 acquireQueued 中被中断过则需要产生一个中断请求原因是线程在调用 acquireQueued 方法的时候是不会响应中断请求的。
cancelAcquire
// cancelAcquire方法
private void cancelAcquire(Node node) {// 如果当前节点为null结束健壮性判断if (node null)return;// node不为null的前提下执行// 将当前node的线程置位null 竞争锁资源跟我没有关系了node.thread null;// 获取当前节点的前驱节点Node pred node.prev;// 前驱节点的状态 0while (pred.waitStatus 0)// 找到前驱中最近的非失效节点node.prev pred pred.prev;// 将第一个不是失效节点的后继节点声明出来Node predNext pred.next;// 将当前节点置位失效节点。给别的Node看的。node.waitStatus Node.CANCELLED;// 如果当前节点是尾节点将尾节点设置为最近的有效节点如果当前节点为尾节点的操作if (node tail compareAndSetTail(node, pred)) {// 用CAS方式将尾节点的next设置nullcompareAndSetNext(pred, predNext, null);} else {int ws;// 中间节点操作// 如果上一个节点不是头节点if (pred ! head 获取上一届点状态是不是有效((ws pred.waitStatus) Node.SIGNAL || // pred需要唤醒后继节点的(ws 0 compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) pred.thread ! null) {Node next node.next;if (next ! null next.waitStatus 0)compareAndSetNext(pred, predNext, next); // 尝试将pred的前驱节点的next指向当前节点的next必须是有效的next节点} else {// 头结点唤醒后继节点unparkSuccessor(node);}node.next node; // help GC}
}