【同步工具类: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 三、技术实现&…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
