图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官
图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官
- ReentrantLock是什么,有什么作用
- ReentrantLock的使用
- ReentrantLock源码解析
- ReentrantLock#lock方法
- FairSync#tryAcquire方法
- NonfairSync#tryAcquire方法
- ReentrantLock#unlock方法
- ReentrantLock的其他方法简介
- ReentrantLock#lockInterruptibly
- ReentrantLock#tryLock
- ReentrantLock#tryLock(带参数)
- ReentrantLock#newCondition
往期文章:
- 人人都能看懂的图解java.util.concurrent并发包源码系列 ThreadPoolExecutor线程池
- 图解java.util.concurrent并发包源码系列,原子类、CAS、AtomicLong、AtomicStampedReference一套带走
- 图解java.util.concurrent并发包源码系列——LongAdder
- 图解java.util.concurrent并发包源码系列——深入理解AQS,看完可以吊打面试官
上一篇文章(图解java.util.concurrent并发包源码系列——深入理解AQS,看完可以吊打面试官 )介绍了AQS的原理和源码,然后使用AQS实现了一个排他锁,这次我们看看Java的并发包里面基于AQS实现的锁工具类——ReentrantLock。本文会介绍ReentrantLock的功能,以及ReentrantLock如何通过AQS实现它的功能,但是不会再介绍AQS的相关原理,关于AQS的原理和源码,可以看上一篇文章。
ReentrantLock是什么,有什么作用
ReentrantLock是可重入锁,当我们有一些资源需要互斥访问,或者有某一块代码需要互斥执行时,可以使用ReentrantLock加锁,对互斥资源和互斥代码块起到一个保护作用,保证同一时刻只会有一个线程访问。
可重入的意思就是当一个线程获取到锁之后,如果它再次获取同一把锁,是可以获取成功的。而不可重入锁则是当前线程获取到锁以后,如果它再次获取,是获取不成功的,也就是自己把自己给阻塞住了,我们上一篇文章最后面的例子就是一个不可重入锁。
ReentrantLock的使用
ReentrantLock的用法非常简单,它提供了lock方法用于获取锁,unlock方法用于释放锁。
调用lock方法后,当前线程会去获取锁,获取不到锁会阻塞等待,直到获取到锁,该方法才会返回。如果重复调用多次lock方法,就重复加锁多次,当前线程不会阻塞(可重入),但是它需要多次释放锁。
调用unlock方法,当前线程就会释放锁,然后唤醒其他阻塞等待获取锁的线程。
我们还是用上一篇文章最后的那个例子,去观察ReentrantLock的作用。
public static void main(String[] args) throws InterruptedException {int[] num = {0};// 创建一个ReentrantLock对象ReentrantLock reentrantLock = new ReentrantLock();Thread[] threads = new Thread[100];for (int i = 0; i < 100; i++) {Thread thread = new Thread(() -> {for (int j = 0; j < 10000; j++) {try {// 加锁reentrantLock.lock();num[0]++;} finally {// 释放锁,防止finally块中是因为防止抛异常导致锁得不到释放reentrantLock.unlock();}}});thread.start();threads[i] = thread;}for (int i = 0; i < threads.length; i++) {threads[i].join();}System.out.println(num[0]);}
可以看到输出结果是正确的。
用法跟我们上一篇文章最后面的例子几乎是一模一样的。首先要创建一个ReentrantLock对象,然在线程try代码块中调用ReentrantLock的lock方法进行加锁,在finally代码块中调用ReentrantLock的unlock方法进行解锁操作,解锁操作在finally块中是因为防止线程抛异常导致锁得不到释放。
ReentrantLock源码解析
接下来看看ReentrantLock的lock方法和unlock方法的内部源码,看看它是怎么实现锁的获取和释放的。
ReentrantLock#lock方法
ReentrantLock#lock
public void lock() {sync.lock();}
可以看到和我们上一篇文章的例子一样,都是有一个内部类,这个内部类肯定继承了AQS。但是它这里没有直接调用AQS的acquire方法,而是调用了一个lock方法,我们进去lock方法看一看。
abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;abstract void lock();// ...省略了下面的代码
}
可以看到Sync继承了AQS,但是Sync本身还是一个抽象类,而lock方法也是一个抽象方法。
可以看到Sync有两个实现类,FairSync和NonfairSync。
也就是说ReentrantLock内部的sync有可能是FairSync,也有可能是NonfairSync。那什么时候是FairSync、什么时候是NonFairSync呢?我们看看ReentrantLock的构造方法就知道了。
/*** 默认非公平锁*/public ReentrantLock() {sync = new NonfairSync();}/*** fair为true时,是公平锁,为false则是非公平锁*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
ReentrantLock可以实现公平锁和非公平锁的功能,默认是非公平锁,如果我们调用有参构造方法,传递的fair参数是true就是公平锁,使用的sync就是FairSync,否则就是非公平锁,使用的就是NonfairSync。
公平锁就是当一个线程获取锁时,会先看一下队列中是否有线程等待获取锁,如果有,那么它会入队列。非公平锁则相反,如果一个线程要获取锁,那么不管三七二十一,先尝试获取一下,如果获取成功,则不用进队列。非公平锁的效率是比公平锁高的,所有默认的就是非公平锁。
我看一下lock方法的具体实现。
static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}// ......省略下面的代码}
FairSync的lock方法直接调用了AQS的acquire方法,与我们上一篇文章的例子一致。
static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}// ......省略下面的代码}
NonfairSync的lock方法,会使用AQS的compareAndSetState方法尝试获取锁,如果CAS成功那么就不需要走acquire方法的逻辑了,如果CAS失败,才会调用AQS的acquire方法。
FairSync#tryAcquire方法
FairSync的lock方法会调用AQS的acquire方法,acquire方法会调用tryAcquire方法,然后会进入到FairSync的tryAcquire方法。
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取AQS中的state变量if (c == 0) { // state等于0表示锁没有被获取if (!hasQueuedPredecessors() && // 查看队列中是否有等待获取锁的线程compareAndSetState(0, acquires)) { // CAS尝试获取锁setExclusiveOwnerThread(current); // 获取锁成功,设置当前线程为占有锁的线程return true; // 返回true,表示告诉AQS获取锁成功}}else if (current == getExclusiveOwnerThread()) { // 锁已被获取,但是获取锁的线程是当前线程int nextc = c + acquires; // state加acquiresif (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc); // 设置state的值return true; // 返回true,表示告诉AQS获取锁成功}return false; // 返回true,表示告诉AQS获取锁不成功}}
FairSync的tryAcquire方法首先会调用AQS的getState方法获取state变量,这里是当state为0才表示锁没有被获取,与我们上一篇文章的例子不一样。
如果state为0,那么锁没有被获取,此时会调用hasQueuedPredecessors()方法检查队列中是否有等待获取锁的线程,如果有,那么当前线程不会获取锁,而是返回false直接进队列排队,如果队列中没有等待获取锁的线程,那么会调用AQS的compareAndSetState方法尝试修改state变量,修改成功表示获取锁成功,此时就会设置当前线程为占有锁的线程,然后方法返回true,表示获取锁成功。
如果state不为0,那么会检查当前线程是否是占有锁的线程,如果是,那么会执行重入的逻辑,然后方法返回true。如果当前线程不是占有锁的线程,那么方法返回false,表示获取锁失败。
下面看一下hasQueuedPredecessors方法是怎么判断队列中是否有线程的。
public final boolean hasQueuedPredecessors() {Node t = tail; // 尾节点Node h = head; // 头节点Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}
h != t 判断头节点和尾节点是否不相等,不相等才有可能有线程在队列中,相等的话表示队列为空,那么肯定是没有线程在队列中的。
如果头节点和尾节点不相等了,那么看h.next头节点的后继节点是否为空,如果为空,那么就表示当前队列已经有等待获取锁的线程。那么为什么头尾节点不相等,并且头节点的next指针为空,就表示有线程已经入队列了呢?还有,什么情况下头节点指针和尾节点指针不相等,但是头节点的next指针为null呢?答案就在AQS初始化队列以及线程节点入队的方法中,也就是AQS的enq方法。
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node())) // A:使用CAS的方式设置头节点指针指向一个空节点tail = head; // B:设置尾指针指向和头指针指向的同一节点} else {node.prev = t; // C:设置node的prev指针指向当前队列的尾节点if (compareAndSetTail(t, node)) { // D:使用CAS的方式设置尾节点指针指向当前nodet.next = node; // E:设置原来的尾节点的next指针指向当前nodereturn t;}}}}
可以看到AQS的enq方法,分成了5个步骤。注意!这里的前提是AQS的队列还未初始化!
假设现在AQS队列还未初始化,在当前线程获取锁之前,有一个线程执行到了enq方法,然后这个线程执行完A、B、C、D四步,也就是它成功初始化了AQS的队列,然后又用CAS的方式修改尾节点的指针指向它自己的node节点,但是就是没有执行代码E,此时是不是头节点指针和尾节点指针不相等(h != t),并且头节点的next指针是null((s = h.next) == null)?此时这个线程已经算是入了队列的,如果此时有其他线程来获取锁(公平锁),那么是应该要排队的。所以当前线程获取锁的时候,执行到hasQueuedPredecessors()方法,就会返回true,表示已经有线程在队列中等待获取锁了。
那么假设现在AQS队列还未初始化,如果在当前线程获取锁之前,有一个线程执行到了enq方法,然这个线程执行完A、B,没有执行后面三步呢?那么此时头节点指针和尾节点指针还是相等的(h != t 不成立),此时这个线程也不算入队列,所以此时如果有别的线程来调用hasQueuedPredecessors()方法,hasQueuedPredecessors()方法放回false(队列中没有等待获取锁的线程),也是正确的。
如果头节点指针和尾节点指针不相等(h != t 为true),然后头节点的next指针不为null((s = h.next) == null 为false),但是头节点的next指针指向的节点对应的线程等于当前线程(s.thread != Thread.currentThread() 为false),那么hasQueuedPredecessors()方法返回false,当前线程可以尝试获取锁,也是正确的,因为很明显这种情况就是我已经在排队了,并且此时轮到我了。如果头节点的next指针指向的节点对应的线程不是当前线程(s.thread != Thread.currentThread() 为true),hasQueuedPredecessors()方法返回true,那么当前线程就只能去排队。
FairSync的tryAcquire方法就介绍到这里。
NonfairSync#tryAcquire方法
NonfairSync的tryAcquire方法里面直接调用了nonfairTryAcquire方法。
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
nonfairTryAcquire方法位于Sync抽象类内部。
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 锁没有被获取,尝试获取锁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;}
进入到Sync的nonfairTryAcquire方法内部,如果判断锁没有被获取(state == 0)就直接先CAS一下,如果CAS成功,该方法就返回true,表示获取锁成功。如果锁已被获取,那么判断当前线程是否是持有锁的线程 (current == getExclusiveOwnerThread()),如果当前线程是持有锁的线程,那么执行锁重入的逻辑,然后方法返回true,表示获取锁成功。否则返回false,表示获取锁失败。
ReentrantLock的lock方法到这里就介绍完了。
ReentrantLock#unlock方法
接下来看一下ReentrantLock的unlock方法的具体逻辑。
public void unlock() {sync.release(1);}
ReentrantLock的unlock方法调用了sync的release方法,这个和我们上一篇文章的例子是一样的。
AQS的release方法会调用tryRelease方法。因为释放锁的操作不需要区分公平还是非公平,都是一样的释放锁的逻辑,所以tryRelease方法就放到了Sync抽象类的内部。也就是说,FairSync和NonfairSync的tryRelease是同一个方法,都是Sync的tryRelease方法。
protected final boolean tryRelease(int releases) {int c = getState() - releases;// 如果当前线程不是持有锁的线程,抛异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// c == 0,表示锁被释放,free设为true表示锁被释放,设置持有锁的线程为nullif (c == 0) {free = true;setExclusiveOwnerThread(null);}// 更新statesetState(c);return free;}
Sync的tryRelease方法首先会调用AQS的getState方法获取state变量,然后计算state变量减去参数releases之后的值c。然后判断如果c等于0,表示锁被释放了,那么设置返回值free为true表示锁被释放,然后setExclusiveOwnerThread(null)设置持有锁的线程为null。然后不管c是否等于0,最后都会调用AQS的setState方法,更新state变量,最后返回free。
如果返回的free为true,表示锁被释放了,如果返回的free为false,表示锁还没有被释放。比如当前线程重复获取锁5次,现在调用了unlock方法3次,那么state等于2,锁还没有被释放,它需要再调用两次,state才等于0,此时锁才被真正释放。
到这里ReentrantLock的unlock方法也介绍完了。
ReentrantLock的其他方法简介
ReentrantLock不仅仅只有lock和unlock两个方法,还有其他获取和释放锁的方法,下面做一个简单介绍,不做深入的描述。
ReentrantLock#lockInterruptibly
public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}
ReentrantLock的lockInterruptibly是以响应中断的方式获取锁,就是说在获取锁的过程中如果其他线程调用了thread.interrupt()方法打断了当前线程,那么当前线程会响应中断,不再继续获取锁。而我们上面介绍的ReentrantLock#lock方法,是不响应中断的,也就是说其他线程调用了当前线程的thread.interrupt()方法,当前线程也不做任何响应。
ReentrantLock#tryLock
public boolean tryLock() {return sync.nonfairTryAcquire(1);}
ReentrantLock的tryLock方法是尝试获取锁,也就是尝试获取一下,成就成,不成就走,也不会阻塞。
ReentrantLock#tryLock(带参数)
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));}
ReentrantLock还有一个带参数的tryLock方法 boolean tryLock(long timeout, TimeUnit unit),该方法和无参的tryLock方法一样,也是尝试获取锁,成就成,不成不会马上走,而是阻塞等待一段时间。timeout参数就是指定阻塞等待的时间,unit则是时间单位。
ReentrantLock#newCondition
public Condition newCondition() {return sync.newCondition();}
ReentrantLock的newCondition可以返回一个Condition对象,Condition 可以实现类似于和synchronized加Object#wait的功能,这个我们后面的文章再介绍吧。
相关文章:

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官
图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官 ReentrantLock是什么,有什么作用ReentrantLock的使用ReentrantLock源码解析ReentrantLock#lock方法FairSync#tryAcquire方法NonfairSync#tryAcquire方法 Reentrant…...

【计算机网络】网络基础(上)
文章目录 1. 网络发展认识协议 2.网络协议初识协议分层OSI七层模型 | TCP/IP网络传输基本流程情况1:同一个局域网(子网)数据在两台通信机器中如何流转协议报头的理解局域网通信原理(故事版本)一般原理数据碰撞结论 情况2:跨一个路由器的两个子网IP地址与…...

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验四 蜂鸣器
目录 前言 一、原理图及知识点介绍 1.1、蜂鸣器原理图: 二、代码分析 前言 第一个实验:51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验一 点亮第一个LED_ManGo CHEN的博客-CSDN博客 第二个实验:51单片机(普中HC6800-EM…...

无向图-已知根节点求高度
深搜板子题,无向图,加边加两个,dfs输入两个参数变量,一个是当前深搜节点,另一个是父节点(避免重复搜索父节点),恢复现场 ///首先完成数组模拟邻接表#include<iostream> #incl…...
RIP动态路由协议 (已过时,逐渐退出舞台)
RIP 路由更新:RIP1/2 每30秒钟广播(255.255.255.255)/组播 (224.0.0.9)一次超时:180秒未收到更新,即标记为不可用(跳数16),240秒收不到,即从路由表中删除 ;跳…...
C++ operator关键字的使用(重载运算符、仿函数、类型转换操作符)
目录 定义operator重载运算符operator重载函数调用运算符operator类型转换操作符 定义 C11 中,operator 是一个关键字,用于重载运算符。通过重载运算符,您可以定义自定义类型的对象在使用内置运算符时的行为。 operator重载用法一般可以分为…...

深度学习笔记-暂退法(Drop out)
背景 在机器学习的模型中,如果模型的参数太多,而训练样本又太少,训练出来的模型很容易产生过拟合的现象。在训练神经网络的时候经常会遇到过拟合的问题,过拟合具体表现在:模型在训练数据上损失函数较小,预…...

使用自适应去噪在线顺序极限学习机预测飞机发动机剩余使用寿命(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
实验5-7 使用函数求1到10的阶乘和 (10 分)
实验5-7 使用函数求1到10的阶乘和 (10 分) 本题要求实现一个计算非负整数阶乘的简单函数,使得可以利用该函数,计算1!2!⋯10!的值。 函数接口定义: double fact( int n ); 其中n是用户传入的参数,其值不超过…...

kafka部署
1.kafka安装部署 1.1 kafaka下载 https://archive.apache.org/dist/kafka/2.4.0/kafka_2.12-2.4.0.tgz Binary downloads是指预编译的软件包,可供直接下载和安装,无需手动编译。在计算机领域中,二进制下载通常指预构建的软件分发包,可以直接安装在系统上并使用 "2.…...

Spring Security6入门及自定义登录
一、前言 Spring Security已经更新到了6.x,通过本专栏记录以下Spring Security6学习过程,当然大家可参考Spring Security5专栏对比学习 Spring Security5专栏地址:security5 Spring Security是spring家族产品中的一个安全框架,核心功能包括…...

开放式蓝牙耳机哪个品牌好用?盘点几款很不错的开放式耳机
相比传统入耳式耳机,开放式耳机因其不入耳不伤耳的开放设计,不仅带来了舒适的佩戴体验,还创造了一种与周围环境互动的全新方式,户外运动过程时也无需担心发生事故,安全性更高。我整理了几款比较好用的开放式耳机给大…...

WebGL: 几个入门小例子
本文罗列几个WebGL入门例子,用于帮助WebGL学习。 一、概述 WebGL (Web Graphics Library)是一组基于Open ES、在Web内渲染3D图形的Javascript APIs。 Ref. from Khronos Group: WebGL WebGL™ is a cross-platform, royalty-free open web standard for a low-lev…...
PAT(Advanced Level)刷题指南 —— 第一弹
一、1001 A+B Format 1. 问题重述 给两个整数,输出这两个数的加和的结果,每三位用逗号分隔。 2. Sample Input -1000000 93. Sample Output -999,9914. 题解 思路:直接将两个整数相加,判断是否为负,是负数则直接输出负号并转为正数;然后将正数转为字符串,按规则每…...
【BASH】回顾与知识点梳理(九)
【BASH】回顾与知识点梳理 九 九. 扩展正则表达式(延伸正规表示法)9.1 egrep命令语法匹配指定模式的行(用法和grep相同)忽略大小写匹配(用法和grep相同)反向匹配(用法和grep相同)显示行号(用法和grep相同)递归搜索目录(用法和grep相同)匹配整词(用法和grep相同)统计匹配行数(用…...

Android 版本 对应的 API版本
Android 14(开发者预览版) 如需详细了解平台变更,请参阅 Android 14 文档。 Android 13(API 级别 33) 如需详细了解平台变更,请参阅 Android 13 文档。 Android 12(API 级别 31、32…...
Django 异常信息 E302 expected 2 blank lines, found 1
在Django中,PEP 8风格指南建议在任何类定义之前都应该有两个空白行,包括视图(views)。错误信息"E302 expected 2 blank lines, found 1"表示在类定义之前只有一个空白行,而Django希望有两个空白行。 要修复…...

2019年09月《全国青少年软件编程等级考试》Python一级真题解析
一、单选题 第1题 关于Python的编程环境,下列的哪个表述是正确的? A:Python的编程环境是图形化的; B:Python只有一种编程环境ipython; C:Python自带的编程环境是IDLE; D&#…...
mybatis如何防止SQL注入
阅读正文: mybatis是如何防止SQL注入的 1、首先看一下下面两个sql语句的区别: <select id"selectByNameAndPassword" parameterType"java.util.Map" resultMap"BaseResultMap"> select id, usernam…...

DoIP学习笔记系列:(三)用CAPL脚本过“安全认证”,$27服务实现
文章目录 1. 如何调用接口通过安全认证?如何新建CAPL工程,在此不再赘述,本章主要分享一下如何在CAPL中调用DoIP接口、diag接口进行DoIP和诊断的测试。 注意:CANoe工具本身的使用没什么难的,所谓会者不难难者不会,各位小伙伴有疑问要多问,多交流,往往难事都只是一层窗户…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
ThreadLocal 源码
ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...

渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用
阻止除自定义标签之外的所有标签 先输入一些标签测试,说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时(如通过点击或键盘导航&…...