【同步工具类:Phaser】
同步工具类:Phaser
- 介绍
- 特性
- 动态调整线程个数
- 层次Phaser
- 源码分析
- state 变量解析
- 构造函数对state变量赋值
- 阻塞方法
- arrive()
- awaitAdvance()
- 业务场景
- 实现CountDownLatch功能
- 代码
- 测试结果
- 实现 CyclicBarrier功能
- 代码展示
- 测试结果
- 总结
介绍
一个可重复使用的同步屏障,功能类似于CyclicBarrier和CountDownLatch,但支持更灵活的使用。该工具类是 JDK 1.7才引入的。功能比 CyclicBarrier 和 CountDownLatch 都要强大。
CyclicBarrier 和 CountDownLatch 的学习地址
特性
动态调整线程个数
CyclicBarrier 所要同步的线程个数是在构造函数中指定的,之后不能更改,而Phaser可以在运行期间动态地调整要同步的线程个数。用来修改同步线程个数的函数有:
public int register() {//注册一个线程return doRegister(1);}
public int bulkRegister(int parties) {if (parties < 0)throw new IllegalArgumentException();if (parties == 0)return getPhase();//注册多个线程return doRegister(parties);}
public int arriveAndDeregister() {//解注册,减少线程个数return doArrive(ONE_DEREGISTER);}
层次Phaser
即 多个Phaser 可以组成 如下的树状结构,可以通过在构造函数中传入父Phaser 来实现

public Phaser(Phaser parent) {//传入Phaser 作为 该对象的父 节点this(parent, 0);}
如上图,如果传Phaser 参数过来,对应的线程数量会相加。对于树状Phaser 上的每个节点来说,可以当作一个独立的Phaser来看待,其运作机制和一个单独的Phaser 是一样的,具体来讲,Phaser 并不用感知子Phaser的存在,当子Phaser中注册的参与者数量大于0时,会把自己向父节点注册,当子Phaser中注册的参与者数量等于0时,会自动向父节点解除注册。父Phaser把子Phaser当作一个正常参与的线程即可。
源码分析
state 变量解析
Phaser 没有基于AQS来实现,但具备AQS的核心特性:state 变量,CAS 操作,阻塞队列等。
首先看下state 变量。
/*** Primary state representation, holding four bit-fields:* state 分为了四个部分。* unarrived -- the number of parties yet to hit barrier (bits 0-15)* parties -- the number of parties to wait (bits 16-31)* phase -- the generation of the barrier (bits 32-62)* terminated -- set if barrier is terminated (bit 63 / sign)** Except that a phaser with no registered parties is* distinguished by the otherwise illegal state of having zero* parties and one unarrived parties (encoded as EMPTY below).** To efficiently maintain atomicity, these values are packed into* a single (atomic) long. Good performance relies on keeping* state decoding and encoding simple, and keeping race windows* short.** All state updates are performed via CAS except initial* registration of a sub-phaser (i.e., one with a non-null* parent). In this (relatively rare) case, we use built-in* synchronization to lock while first registering with its* parent.** The phase of a subphaser is allowed to lag that of its* ancestors until it is actually accessed -- see method* reconcileState.*/private volatile long state;
从英文注释中我们可以得知:这个64位的state变量被拆分成4部分。如图所示
最高位0表示未同步完成,1表示同步完成。初始最高位为0.可以看下如下几个函数对state 的各个部分组成的获取。获取当前轮数(32-62位)
/*** Returns the current phase number. The maximum phase number is* {@code Integer.MAX_VALUE}, after which it restarts at* zero. Upon termination, the phase number is negative,* in which case the prevailing phase prior to termination* may be obtained via {@code getPhase() + Integer.MIN_VALUE}.** @return the phase number, or a negative value if terminated*/public final int getPhase() {//无符号右移(高位补0) 32位。//然后强制int .则最高位还是 是否为同步信息。//如果是负数,说明最高位是1,已经完成了同步return (int)(root.state >>> PHASE_SHIFT);}private static final int PHASE_SHIFT = 32;
判断当前轮数同步是否完成。
/*** Returns {@code true} if this phaser has been terminated.** @return {@code true} if this phaser has been terminated*/public boolean isTerminated() {//判断整个值是否是负数即可。最高位也是符号位。return root.state < 0L;}
获取总注册线程数
/*** Returns the number of parties registered at this phaser.** @return the number of parties*/public int getRegisteredParties() {return partiesOf(state);}private static int partiesOf(long s) {//先将s 转成int.相当于把高位 是否完成同步和轮数舍弃了。//只剩下低位的总线程数和未达到线程数。然后向右无符号右移16位(高位补0)。//就只剩下了总线程数return (int)s >>> PARTIES_SHIFT;}private static final int PARTIES_SHIFT = 16;
获取未达到的线程数
/*** Returns the number of registered parties that have not yet* arrived at the current phase of this phaser. If this phaser has* terminated, the returned value is meaningless and arbitrary.** @return the number of unarrived parties*/public int getUnarrivedParties() {return unarrivedOf(reconcileState());}
private static int unarrivedOf(long s) {// 首先将state强转为int,舍弃高位的 是否完成同步和轮数信息int counts = (int)s;//不为空的话,则和 0xffff 进行与操作,截取 低16位。即未到达的线程数return (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);}private static final int EMPTY = 1;private static final int UNARRIVED_MASK = 0xffff; // to mask ints
构造函数对state变量赋值
如果能理解state 变量的分布图。64位的划分,划分为4个部分。 那么就很容易理解构造函数的初始了。
/*** Creates a new phaser with the given parent and number of* registered unarrived parties. When the given parent is non-null* and the given number of parties is greater than zero, this* child phaser is registered with its parent.** @param parent the parent phaser* @param parties the number of parties required to advance to the* next phase* @throws IllegalArgumentException if parties less than zero* or greater than the maximum number of parties supported*/public Phaser(Phaser parent, int parties) {//初始化未到达的线程数,不能大于2 的16次方。上面有个state 变量划分图if (parties >>> PARTIES_SHIFT != 0)throw new IllegalArgumentException("Illegal number of parties");//初始轮数为0int phase = 0;this.parent = parent;//判断parent 是否为null。不为null.则把自己注册到父对象中if (parent != null) {final Phaser root = parent.root;this.root = root;this.evenQ = root.evenQ;this.oddQ = root.oddQ;if (parties != 0)phase = parent.doRegister(1);}else {this.root = this;this.evenQ = new AtomicReference<QNode>();this.oddQ = new AtomicReference<QNode>();}//如果parties为0,则赋值EMPTY.//不为0,则将 phare 左移 32位 轮数赋值// 将parties 左移16位 总线程数数,初始值和未达到数一样// parties 未达到线程数// 最后将三个数 进行或运算 (只有有一个是1,则结果为1)this.state = (parties == 0) ? (long)EMPTY :((long)phase << PHASE_SHIFT) |((long)parties << PARTIES_SHIFT) |((long)parties);}private static final int PARTIES_SHIFT = 16;private static final int PHASE_SHIFT = 32;private static final int EMPTY = 1;
阻塞方法
如下图所示,右边的主线程会调用awaitAdvance()进行阻塞。左边的arrive()会对state进行cas的累减操作。当未到达的线程数减到0时,唤醒右边阻塞的主线程。

在这里,阻塞使用的是一个称为Treiber Stack的数据结构,而不是AQS的双向链表。Treiber Stack是一个单向链表,出栈和入栈都在链表头部,所以只需要一个head指针,而不需要tail指针。
Treiber Stack 代码
static final class QNode implements ForkJoinPool.ManagedBlocker {final Phaser phaser;final int phase;final boolean interruptible;final boolean timed;boolean wasInterrupted;long nanos;final long deadline;volatile Thread thread; // nulled to cancel wait 每个Node 对应一个Thread 对象QNode next; //单项链表,QNode(Phaser phaser, int phase, boolean interruptible,boolean timed, long nanos) {this.phaser = phaser;this.phase = phase;this.interruptible = interruptible;this.nanos = nanos;this.timed = timed;this.deadline = timed ? System.nanoTime() + nanos : 0L;thread = Thread.currentThread();}
}
/*** Heads of Treiber stacks for waiting threads. To eliminate* contention when releasing some threads while adding others, we* use two of them, alternating across even and odd phases.* Subphasers share queues with root to speed up releases.*///为了减少并发冲突,这里定义了2个链表,也就是2个Treiber Stack。private final AtomicReference<QNode> evenQ;private final AtomicReference<QNode> oddQ;private AtomicReference<QNode> queueFor(int phase) {//当phase 为奇数的时候,阻塞线程放在addQ里面// 为偶数的时候,阻塞线程放在eventQ里面return ((phase & 1) == 0) ? evenQ : oddQ;}
arrive()
对state 变量进行操作,然后唤醒线程。
arrive()和arriveAndDeregister()内部调用的都是doArrive() 函数。区别在于前者只是把未达到线程数减一。后者则把未到达线程数和下一轮的总线程数都减一。
public int arrive() {return doArrive(ONE_ARRIVAL);}private static final int ONE_ARRIVAL = 1;
public int arriveAndDeregister() {return doArrive(ONE_DEREGISTER);}//65536private static final int ONE_PARTY = 1 << PARTIES_SHIFT;//65537private static final int ONE_DEREGISTER = ONE_ARRIVAL|ONE_PARTY;
doArrive()
把未到达线程数减一,减一之和,还未到1(空置设置的是1,这里和jdk1.7有点区别)。什么都不做。直接返回。如果到1后。说明所有线程到达。 然后会处理两件事:
1)重置state,把state 的未到达线程个数重置到总的注册的线程数中。同时phaser加1
2).唤醒队列中的线程
private int doArrive(int adjust) {final Phaser root = this.root;for (;;) {long s = (root == this) ? state : reconcileState();int phase = (int)(s >>> PHASE_SHIFT);//小于0,直接返回if (phase < 0)return phase;int counts = (int)s;int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);if (unarrived <= 0)throw new IllegalStateException(badArrive(s));if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adjust)) {//CAS 减一if (unarrived == 1) {//所有线程到达long n = s & PARTIES_MASK; // base of next stateint nextUnarrived = (int)n >>> PARTIES_SHIFT;if (root == this) {//父 Phaser 为空,调用自己的 onAdvance()if (onAdvance(phase, nextUnarrived))n |= TERMINATION_BIT;//最高位置为1else if (nextUnarrived == 0)n |= EMPTY;elsen |= nextUnarrived;//下一轮的未到达数等于总的线程数int nextPhase = (phase + 1) & MAX_PHASE;n |= (long)nextPhase << PHASE_SHIFT;//重置stateUNSAFE.compareAndSwapLong(this, stateOffset, s, n);//释放所有线程releaseWaiters(phase);}else if (nextUnarrived == 0) { // propagate deregistration//父 Phaser 不为空,调用父 的doArrive()并且下个未到达==0.//则把未到的线程和下一轮未到的线程都减一phase = parent.doArrive(ONE_DEREGISTER);UNSAFE.compareAndSwapLong(this, stateOffset,s, s | EMPTY);}else// 父 Phaser 不为空,调用父 的doArrive() 减一个phase = parent.doArrive(ONE_ARRIVAL);}//没有全部到达,直接返回return phase;}}}
releaseWaiters()
private void releaseWaiters(int phase) {QNode q; // first element of queueThread t; // its thread//根据phase是奇数还是偶数来找对应的栈AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;//遍历整个栈while ((q = head.get()) != null &&//如果当前节点的phase 不等于当前Phaser的phase,说明该节点不是当前轮的,而是前一轮的。//需要被释放并唤醒q.phase != (int)(root.state >>> PHASE_SHIFT)) {if (head.compareAndSet(q, q.next) &&(t = q.thread) != null) {q.thread = null;LockSupport.unpark(t);}}}
awaitAdvance()
internalAwaitAdvance 方法主要是调用了ForkJoinPool.manageBlock函数。目的是把Node对应的线程阻塞。
public int awaitAdvance(int phase) {final Phaser root = this.root;//root==this 表示没有树状结构。只有一个Phaserlong s = (root == this) ? state : reconcileState();int p = (int)(s >>> PHASE_SHIFT);if (phase < 0)//phase 已经结束,不用阻塞了,直接返回return phase;if (p == phase)//阻塞在这一层上面return root.internalAwaitAdvance(phase, null);return p;}
业务场景
实现CountDownLatch功能
前面我们用 CountDownLatch实现了 主线程等待10 个线程预加载数据完成后再执行 加载其他组件功能。
代码可以参考之前的CountDownLatch文章.
代码
public class DoThing extends Thread{private final Phaser startPhaser;private final Phaser donePhaser;public DoThing(Phaser startSignal, Phaser doneSignal) {this.startPhaser = startSignal;this.donePhaser = doneSignal;}@Overridepublic void run() {try {//开始阻塞了,等待主线程开启System.out.println(Thread.currentThread().getName()+" 开始阻塞等待了...");startPhaser.awaitAdvance(startPhaser.getPhase());doWork();donePhaser.arrive();} catch (InterruptedException ex) {}}public void doWork() throws InterruptedException {System.out.println(Thread.currentThread().getName() + " 开始干活了,DB 中的数据加载到本地缓存中");Thread.sleep(1000);}
}
/*** @author :echo_黄诗* @description:利用Phaser 来实现 CountDownLatch 功能* @date :2023/3/3 18:50*/
public class Run {public static void main(String[] args) {Phaser startPhaser = new Phaser(1);Phaser donePhaser = new Phaser(10);for (int i = 0; i < 10; ++i) // create and start threadsnew Thread(new DoThing(startPhaser, donePhaser)).start();doSomethingElse();//开始加载数据,startPhaser.arrive();//主线程阻塞,等待数据加载完成donePhaser.awaitAdvance(donePhaser.getPhase());doSomethingElse();System.out.println("数据加载完成,开始启动其他组件,包括dubbo组件");}public static void doSomethingElse(){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}
}
测试结果

实现 CyclicBarrier功能
前面CyclicBarrier章节 我们利用 CyclicBarrier 实现了10个求职者 一起来了后开始笔试,然后一起面试的功能。之前的CyclicBarrier的代码可以参考.
代码展示
工具类
public class Utils {/*** 模拟在路上方法*/public static void doOnTheWay() {doCostTime(2000);}/*** 模拟笔试过程*/public static void doWriteExam() {doCostTime(3000);}/*** 模拟面试过程*/public static void doInterview() {doCostTime(5000);}private static void doCostTime(int time) {Random random = new Random();try {//随机休眠时间int count = random.nextInt(time);// System.out.println(count);Thread.sleep(count);} catch (InterruptedException e) {e.printStackTrace();}}
}
面试类
public class Interview extends Thread{private Phaser phaser;public Interview(Phaser phaser) {this.phaser = phaser;}@Overridepublic void run() {phaser.arrive();Utils.doInterview();System.out.println(Thread.currentThread().getName() + " 面试完啦.....");}
}
笔试类
public class WriteExam extends Thread{private Phaser phaser;public WriteExam(Phaser phaser) {this.phaser = phaser;}@Overridepublic void run() {Utils.doWriteExam();phaser.arrive();System.out.println(Thread.currentThread().getName() + " 笔试做完了....");}
}
来公司路上
public class OnTheWay extends Thread{private Phaser phaser;public OnTheWay(Phaser phaser) {this.phaser = phaser;}@Overridepublic void run() {Utils.doOnTheWay();System.out.println(Thread.currentThread().getName() + " 已经来公司了...");phaser.arrive();}
}
运行类
此处需要注意,因为是主线程执行 awaitAdvance。所以需要先执行子线程。不然主线程执行就直接阻塞了。
子线程都没有机会执行。因为子线程是靠主线程启动的。
/*** @author :echo_黄诗* @description:用Phaser 实现CyclicBarrier功能* @date :2023/3/3 19:08*/
public class Run {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor(10,20,100, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));Phaser phaser=new Phaser(10);for (int i=0;i<10;i++){threadPoolExecutor.execute(new OnTheWay(phaser));}phaser.awaitAdvance(phaser.getPhase());for (int i=0;i<10;i++){threadPoolExecutor.execute(new WriteExam(phaser));}phaser.awaitAdvance(phaser.getPhase());for (int i=0;i<10;i++){threadPoolExecutor.execute(new Interview(phaser));}phaser.awaitAdvance(phaser.getPhase());}
}
测试结果
从打印截图可以看出,使用Phaser 能很好的实现了CyclicBarrier的同步阻塞功能。
但是目前还不能实现其回调函数的功能。

总结
Phaser 相比CountDownLatch ,Semaphore,CyclicBarrier 的源码,实现上还是复杂的多。但使用上面比较简单。这里只是给大家一个例子。知道该如何用。目前分析源码也不是很透彻。如果要彻底弄清楚,可以参考 Java并发实现原理这本教程。
相关文章:
【同步工具类:Phaser】
同步工具类:Phaser介绍特性动态调整线程个数层次Phaser源码分析state 变量解析构造函数对state变量赋值阻塞方法arrive()awaitAdvance()业务场景实现CountDownLatch功能代码测试结果实现 CyclicBarrier功能代码展示测试结果总结介绍 一个可重复使用的同步屏障,功能…...
Linux命令·rmdir
今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具…...
从0开始自制解释器——综述
作为一个程序员,自制自己的编译器一直是一个梦想。之前也曾为了这个梦想学习过类似龙书、虎书这种大部头的书,但是光看理论总有一些云里雾里的感觉。看完只觉得脑袋昏昏沉沉并没有觉得有多少长进。当初看过《疯狂的程序员》这本书,书里说&…...
【spring】spring5特性
1、整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方 法在代码库中删除 日志框架 2、Spring 5.0 框架自带了通用的日志封装 (1)Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j…...
曹云金回归、于谦电影杀青,德云社想不火都难
说起民间最大的相声社团,首屈一指的要属德云社,之所以说德云社最大,主要是优秀相声演员够多。德云社在郭德纲的带领下,如今已经是人才济济,听说最近队伍会进一步壮大,前徒弟曹云金也要回归了。 当年曹云金作…...
从入门到精通:数据库设计规范指南
当我们开始设计数据库时,我们需要确保它是可靠和可扩展的。为了实现这一目标,我们需要遵循一些数据库设计规范。本文将介绍一些数据库设计规范,以确保您的数据库能够满足当前和未来的业务需求。 目录 一、命名规则 二、数据类型 三、索引…...
js 求解《初级算法》8.字符串转换整数(atoi)
一、题目描述 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数 算法如下: 读入字符串并丢弃无用的前导空格 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符ÿ…...
Vue学习笔记(5)
5.1 其他常用内置指令 5.1.1 v-text v-text是Vue.js中常用的内置指令之一,用于将数据绑定到DOM元素的文本内容。与双花括号({{ }})类似,v-text指令也可以将Vue实例中的数据渲染到页面上。 使用v-text指令时,Vue会将指…...
LeetCode 面试题 05.02. Binary Number to String LCCI【字符串,数学】中等
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...
数据结构 “串“ 的补充提升与KMP算法及其优化的具体实现
❤️作者主页:微凉秋意 ✅作者简介:后端领域优质创作者🏆,CSDN内容合伙人🏆,阿里云专家博主🏆 ✨精品专栏:C面向对象 🔥系列专栏:数据结构与课程设计 文章目录…...
如何使用Spring Cloud搭建MQ(Message Queue)消息队列
Spring Cloud是一个开源框架,用于构建基于微服务架构的应用程序。它提供了多种工具和技术,用于实现各种微服务模式,并使它们易于管理和部署。MQ(消息队列)则是一种重要的异步通信机制,用于在不同的应用程序…...
iphone备忘录删除怎么恢复?分享苹果数据找回办法
手机备忘录上写记录,这是不少上班族的小习惯。因为它可以先记录紧急事务,然后再慢慢的解决。也可以把我们一些重要的账号密码存在备忘录里,方便在何时何地直接登入使用。那么如果我们不小心删除了iphone备忘录呢?碰到这种事该怎么办呢?有没…...
【PPT】《我去!还有这种网站?》-知识点目录
《我去!还有这种网站?》 1. Vega AI 输入提示: girl,粉头发2. 物理画线:休闲小游戏 3. Dialogue:影视台词搜索 4. Can you run it:游戏设备要求查询 5. Deviceshots:使用设备边…...
SQL 将查询结果插入到另一张表中
INSERT INTO (1) 如果两张表(导出表和目标表)的字段一致,并且希望插入全部数据,可以用这种方法: INSERT INTO 目标表 SELECT * FROM 来源表 WHERE 条件;例如,要将 test 表插入到 n…...
代码随想录算法训练营day48 | 动态规划 121 买卖股票的最佳时机 122 买卖股票的最佳时机II
day48121. 买卖股票的最佳时机1.确定dp数组(dp table)以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组122.买卖股票的最佳时机II121. 买卖股票的最佳时机 题目链接 解题思路: 动规五部曲分析如下:…...
MediaTek 天玑 8000 5G移动平台详细参数
MediaTek 天玑 8000 移动平台 采用先进的 台积电 5nm 工艺,拥有出众的性能和能效,为高端智能手机用户提供出色的高帧率游戏和 5G 移动体验。 天玑 8000 采用了 MediaTek 诸多先进技术,内置 MediaTek Imagiq 780影像引擎、第五代 AI 处理器APU…...
Kafka
这里写目录标题1.Kafka1.1 Kafka概述1.2 kafka安装和配置1.3 入门案例1.4 kafka生产者详解1.4.1 生产者的参数1.Kafka 1.1 Kafka概述 Kafka 是一个分布式流媒体平台,类似于消息队列或企业消息传递系统。 producer:发布消息的对象称之为主题生产者(Ka…...
数据结构——第三章 栈与队列(2)
栈的运用1.括号匹配2.表达式求值2.1.算术表示式的形式2.2.后缀表达式求值2.3.将算术表达式转换为后缀表达式2.4.算术表达式直接求值3.栈与递归3.1.递归算法3.2.栈与函数调用3.3.递归工作与递归函数3.4.递归到非递归的转换1.括号匹配 void matching(char str[]) {//创建空栈Lin…...
【Linux学习】基础IO——理解缓冲区 | 理解文件系统
🐱作者:一只大喵咪1201 🐱专栏:《Linux学习》 🔥格言:你只管努力,剩下的交给时间! 基础IO☕理解缓冲区🧃缓冲区的共识🧃缓冲区的位置🧃缓冲区的刷…...
RHCSA-重置root密码(3.3)
方法1:rd.break (1)首先重启系统,在此页面按e键,在屏幕上显示内核启动参数 (2)知道linux这行,末尾空格后输入rd.break,然后按ctrlx (3)查看&#…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
