当前位置: 首页 > news >正文

Java定时任务实现方案(五)——时间轮

时间轮

这篇笔记,我们要来介绍实现Java定时任务的第五个方案,使用时间轮,以及该方案的优点和缺点。
​ 时间轮是一种高效的定时任务调度算法,特别适用于大量定时任务的场景。时间轮的定时任务实现,可以使用DelayQueue作为基础。

​ 在使用时间轮算法之前,我要来简单介绍一下时间轮的一些概念,便于大家理解。

​ 我们可以把时间轮想象成一个时钟,这个时钟被划分为12个格子,每个格子代表一段时间间隔,我们假设是1000ms(1s),每个格子里存放着这个时间段内需要执行的所有定时任务。时钟上有一根指针,当指针指向哪个格子时,格子内的定时任务就可以开始执行或者准备执行了,每过一个时间间隔,指针就向前移动一格,执行下一个时间段的定时任务。

​ 在我们上面的举例当中,12个格子叫做时间槽,时间轮可以被划分为多个固定大小的时间槽,每一个时间槽代表一个时间段;时钟上的指针,用来指示当前需要执行定时任务的时间槽;

​ 我们日常中的时钟,是有三个指针的,我们的时间轮也可以拓展成多级时间轮,支持更长时间的定时任务调度。

实现
1.单个时间槽的实现

​ 因为我们要使用DelayQueue作为基础实现时间轮,所以我们首先要有一个实现了Delay接口的类来承接我们的单个定时任务,如果对如何使用DelayQueue不了解的,可以去看一下我的另一篇关于使用DelayQueue实现定时任务的小作文哦。

    private static class TimerTask implements Delayed {private final Runnable task;private final long expiration;public TimerTask(Runnable task, long expiration) {this.task = task;this.expiration = expiration;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(this.expiration, ((TimerTask) o).expiration);}public void run() {task.run();}}

​ 接着,因为我们的时间轮是处理一个时间槽内的一批定时任务,所以我们还需要一个存储一个时间槽内所有定时任务的集合类,或者说一个逻辑上的时间槽任务类。

​ 这个逻辑时间槽任务类,我们可以把时间槽也当成一个定时任务,存放在时间轮中,因此,也要实现Delay接口,任务执行的时间,就是这个时间槽的表示的时间段的起始时间。

​ 在这个时间槽类中,我们其实就是使用DelayQueue来存取我们单个的定时任务,说白了,就是将DelayQueue实现定时任务的方法进行封装,我们要对外暴露添加任务和执行任务的方法,为了能够实现时间槽的复用,当时间槽中的定时任务清空之后,我们要重置这个时间槽的时间。

       /*** TimerTaskList类实现了Delayed接口,用于管理一组具有延迟执行需求的任务* */private static class TimerTaskList implements Delayed {// 任务的过期时间,即任务应该被执行的时间点private long expiration;private List<TimerTask> tasks;// 使用DelayQueue来存储具有延迟执行需求的TimerTask对象private DelayQueue<TimerTask> queue = new DelayQueue<>();// ExecutorService用于执行任务,它是在类初始化时通过构造函数传入的private final ExecutorService executorService;/*** 构造函数,初始化ExecutorService** @param executorService 用于执行任务的线程池*/public TimerTaskList(ExecutorService executorService) {this.executorService = executorService;this.tasks = new ArrayList<>();}/*** 向队列中添加一个新的TimerTask任务** @param task 要添加的TimerTask对象*/public void addTask(TimerTask task) {tasks.add(task);queue.offer(task);}/*** 设置任务的过期时间* 只有当expiration尚未设置(即值为0)时,才更新expiration值** @param expiration 任务的过期时间* @return 如果expiration成功设置,则返回true;否则返回false*/public boolean setExpiration(long expiration) {if (this.expiration == 0) {this.expiration = expiration;return true;}return false;}/*** 清除所有任务并重置过期时间** 本方法旨在清除所有当前持有的任务,并将过期时间重置为0* 这在需要重新初始化或清理资源时特别有用*/public void clearTasks(){// 清除所有任务tasks.clear();// 重置过期时间为0,表示没有过期时间expiration = 0;}public List<TimerTask> getTasks(){return tasks;}/*** 执行所有任务** 此方法遍历任务列表,并依次执行每个任务的方法run* 在所有任务执行完毕后,调用clearTasks方法清除任务列表*/public void executeTasks(){// 遍历任务列表for(TimerTask task:tasks){// 执行任务的run方法task.run();}// 所有任务执行完毕后,清除任务列表clearTasks();}/*** 执行队列中的所有任务* 如果队列不为空,则通过executorService执行每个任务* 在所有任务执行完毕后,清除expiration值*/public void run() {if (!queue.isEmpty()) {executorService.execute(() -> {while (!queue.isEmpty()) {try {queue.take().run();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}clearExpiration();});}}/*** 获取当前设置的过期时间** @return 当前的expiration值*/public long getExpiration() {return expiration;}/*** 清除过期时间设置,将expiration重置为0*/public void clearExpiration() {expiration = 0;}/*** 实现Delayed接口的getDelay方法* 计算当前时间与过期时间之间的差值,以确定延迟时间** @param unit 时间单位* @return 剩余的延迟时间,以指定的时间单位表示*/@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}/*** 实现Delayed接口的compareTo方法* 用于比较两个TimerTaskList对象的过期时间** @param o 另一个Delayed对象* @return 如果当前对象的过期时间小于、等于或大于参数对象的过期时间,则分别返回负数、零或正数*/@Overridepublic int compareTo(Delayed o) {return Long.compare(this.expiration, ((TimerTaskList) o).expiration);}}
2.将时间槽组成时间轮

​ 完成了单个时间槽的实现之后,剩下的就简单很多了,将上面的两个类作为时间轮类的内部类,将时间槽作为时间轮的一个定时任务来组成我们的时间轮。根据我们前面对于时间轮的描述,时间轮其实就是多个时间槽围成一圈变成了一个时间轮。我们在学数据结构的时候,学过循环队列,把循环队列中的元素换成我们的时间槽,再加上一个指针指向时间槽,就真正构成了一个单层的时间轮了。注意,这里的指针得使用原子类来保证并发安全,因为我们的时间轮可能被多个线程同时使用。

​ 如果时间轮的每一个时间槽存的也是一个时间轮,那么多构成了多级时间轮,对于多级时间轮,我们只需要在最低级的时间轮中放置定时任务,不需要放置子轮,对于高级的时间轮,我们只需要放置子轮,不需要放置定时任务。

​ 我们的时间轮要对外提供启动时间轮的方法,添加定时任务到多级时间轮中的方法,更新移动指针的方法,以及执行时间轮中一批定时任务的方法。

public class TimingWheel {// 每个时间槽的时间间隔,单位毫秒private static final int TICK_DURATION = 1000;// 时间轮的大小,即每个时间轮包含的时间槽数量private static final int WHEEL_SIZE = 20;// 子时间轮列表,用于处理超过当前时间轮处理能力的任务private final List<TimingWheel> subWheels;// 当前时间轮的级别,从0开始,级别越高,表示处理的时间跨度越大private final int level;// 最大时间轮级别,用于确定时间轮的深度private final int maxLevel;// 共享的延迟队列,用于存储所有到期的任务列表private final DelayQueue<TimerTaskList> sharedQueue;// 时间槽数组,用于存储任务列表private final TimerTaskList[] buckets = new TimerTaskList[WHEEL_SIZE];// 时间轮的当前刻度,使用原子长整型确保线程安全private final AtomicLong tick = new AtomicLong(0);// 任务执行线程池private final ExecutorService executorService;/*** 构造函数,初始化时间轮** @param maxLevel 最大时间轮级别,用于确定时间轮的深度*/public TimingWheel(int maxLevel){this.level = maxLevel;this.maxLevel = maxLevel;this.subWheels = new ArrayList<>();this.sharedQueue = new DelayQueue<>();this.executorService = Executors.newFixedThreadPool(WHEEL_SIZE+1);if(maxLevel <= 0){for(int i = 0;i < WHEEL_SIZE;i++){buckets[i] = new TimerTaskList(this.executorService);}}else{for(int i = 0; i < WHEEL_SIZE;i++){subWheels.add(new TimingWheel(maxLevel - 1, maxLevel,this.sharedQueue,this.executorService));}}}/*** 私有构造函数,用于创建子时间轮** @param level 当前时间轮的级别* @param maxLevel 最大时间轮级别* @param sharedQueue 共享的延迟队列* @param executorService 任务执行线程池*/private TimingWheel(int level, int maxLevel,DelayQueue<TimerTaskList> sharedQueue,ExecutorService executorService) {this.level = level;this.maxLevel = maxLevel;this.subWheels = new ArrayList<>();this.sharedQueue = sharedQueue;this.executorService = executorService;if (level > 0) {for (int i = 0; i < WHEEL_SIZE; i++) {subWheels.add(new TimingWheel(level - 1, maxLevel,this.sharedQueue,this.executorService));}}else{for (int i = 0; i < WHEEL_SIZE; i++) {buckets[i] = new TimerTaskList(this.executorService);}}}/*** 启动时间轮,开始处理任务*/public void start() {executorService.execute(() -> {while (true) {try {TimerTaskList bucket = sharedQueue.take();long ticks = (bucket.getExpiration() - System.currentTimeMillis())/ TICK_DURATION;if (ticks > tick.get()) {Thread.sleep((ticks - tick.get()) * TICK_DURATION);}processTasks(bucket);// 更新时间轮指针tick.set(ticks);} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}});}/*** 添加任务到时间轮** @param task 要添加的任务* @param delay 延迟时间,单位毫秒*/public void addTask(Runnable task, long delay) {long currentTime = System.currentTimeMillis();long expiration = currentTime + delay;//计算定时任务要放在哪一个时间槽中//下一层的时钟长度(如果level为0,那就是一个槽的时间长度)long ts = TICK_DURATION * (long)Math.pow(WHEEL_SIZE,level);//总时钟步数int ticks = (int) ((delay) /ts);int bucketIndex = ticks % WHEEL_SIZE;TimerTaskList bucket = buckets[bucketIndex];if (level > 0) {// 修正传递给子时间轮的延迟时间subWheels.get(bucketIndex).addTask(task, delay);} else {bucket.addTask(new TimerTask(task,expiration));if (bucket.setExpiration(expiration)) {sharedQueue.offer(bucket);}}}/*** 处理任务列表中的任务** @param bucket 任务列表*/private void processTasks(TimerTaskList bucket) {bucket.run();
//        bucket.executeTasks();if (level < maxLevel) {for (TimingWheel subWheel : subWheels) {subWheel.advanceClock();}}}/*** 推动时间轮前进*/public void advanceClock() {tick.incrementAndGet();for (TimingWheel subWheel : subWheels) {subWheel.advanceClock();}}/*** TimerTask类,表示一个具有延迟执行需求的任务*/private static class TimerTask implements Delayed {private final Runnable task;private final long expiration;public TimerTask(Runnable task, long expiration) {this.task = task;this.expiration = expiration;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(this.expiration, ((TimerTask) o).expiration);}public void run() {task.run();}}/*** TimerTaskList类实现了Delayed接口,用于管理一组具有延迟执行需求的任务* 它使用DelayQueue来存储这些任务,并在满足执行条件时通过ExecutorService来执行它们*/private static class TimerTaskList implements Delayed {// 任务的过期时间,即任务应该被执行的时间点private long expiration;private List<TimerTask> tasks;// 使用DelayQueue来存储具有延迟执行需求的TimerTask对象private DelayQueue<TimerTask> queue = new DelayQueue<>();// ExecutorService用于执行任务,它是在类初始化时通过构造函数传入的private final ExecutorService executorService;/*** 构造函数,初始化ExecutorService** @param executorService 用于执行任务的线程池*/public TimerTaskList(ExecutorService executorService) {this.executorService = executorService;this.tasks = new ArrayList<>();}/*** 向队列中添加一个新的TimerTask任务** @param task 要添加的TimerTask对象*/public void addTask(TimerTask task) {tasks.add(task);queue.offer(task);}/*** 设置任务的过期时间* 只有当expiration尚未设置(即值为0)时,才更新expiration值** @param expiration 任务的过期时间* @return 如果expiration成功设置,则返回true;否则返回false*/public boolean setExpiration(long expiration) {if (this.expiration == 0) {this.expiration = expiration;return true;}return false;}/*** 清除所有任务并重置过期时间* * 本方法旨在清除所有当前持有的任务,并将过期时间重置为0* 这在需要重新初始化或清理资源时特别有用*/public void clearTasks(){// 清除所有任务tasks.clear();// 重置过期时间为0,表示没有过期时间expiration = 0;}public List<TimerTask> getTasks(){return tasks;}/*** 执行所有任务* * 此方法遍历任务列表,并依次执行每个任务的方法run* 在所有任务执行完毕后,调用clearTasks方法清除任务列表*/public void executeTasks(){// 遍历任务列表for(TimerTask task:tasks){// 执行任务的run方法task.run();}// 所有任务执行完毕后,清除任务列表clearTasks();}/*** 执行队列中的所有任务* 如果队列不为空,则通过executorService执行每个任务* 在所有任务执行完毕后,清除expiration值*/public void run() {if (!queue.isEmpty()) {executorService.execute(() -> {while (!queue.isEmpty()) {try {queue.take().run();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}clearExpiration();});}}/*** 获取当前设置的过期时间** @return 当前的expiration值*/public long getExpiration() {return expiration;}/*** 清除过期时间设置,将expiration重置为0*/public void clearExpiration() {expiration = 0;}/*** 实现Delayed接口的getDelay方法* 计算当前时间与过期时间之间的差值,以确定延迟时间** @param unit 时间单位* @return 剩余的延迟时间,以指定的时间单位表示*/@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}/*** 实现Delayed接口的compareTo方法* 用于比较两个TimerTaskList对象的过期时间** @param o 另一个Delayed对象* @return 如果当前对象的过期时间小于、等于或大于参数对象的过期时间,则分别返回负数、零或正数*/@Overridepublic int compareTo(Delayed o) {return Long.compare(this.expiration, ((TimerTaskList) o).expiration);}}
}
优点
1.高效的时间管理

​ 时间轮将时间划分为固定大小的时间槽,每个时间槽代表一个时间段,通过指针逐个扫描这些时间槽,可以高效地管理和调度定时任务,避免了频繁的线程唤醒和上下文切换。

2.低延迟和高吞吐量

​ 由于时间轮采用的是批量处理到期任务的方式,因此可以在较低的延迟下出来大量的定时任务,提高系统的吞吐量。

3.扩展性强

​ 时间轮可以通过多级时间轮的设计来支持更长的延迟时间,子时间轮可以处理更长时间的任务,从而使得整个系统能够灵活应对不同延迟需求的任务

4.简单易懂

​ 时间轮的结构和工作原理相对简单,易于理解和实现,这使得我们可以快速上手,并且在调试和维护的时候也更方便

缺点
1.固定的时间槽大小

​ 时间轮的时间槽大小是固定的,这可能导致某些场景下的精度不足。如果时间槽设置得太小,会增加内存占用;如果设置得太大,则可能影响定时任务的精确度。

2.多级时间轮的复杂性

​ 为了处理更长的延迟时间,可以采用多级时间轮的设计,但是这种设计会增加系统的复杂性。

3.任务堆积问题

​ 当大量任务集中在同一个时间槽内时,可能会导致任务堆积,进而影响任务的执行效率和响应时间。

4.时钟漂移

​ 在分布式系统中,不同节点的时钟可能存在偏差,这会影响时间轮的准确性。

相关文章:

Java定时任务实现方案(五)——时间轮

时间轮 这篇笔记&#xff0c;我们要来介绍实现Java定时任务的第五个方案&#xff0c;使用时间轮&#xff0c;以及该方案的优点和缺点。 ​ 时间轮是一种高效的定时任务调度算法&#xff0c;特别适用于大量定时任务的场景。时间轮的定时任务实现&#xff0c;可以使用DelayQueue…...

【事务管理】

目录 一. 介绍与操作二. Spring事务管理三. 事务四大特性 \quad 一. 介绍与操作 \quad \quad 二. Spring事务管理 \quad 推荐加在经常进行增删改的方法上 \quad 三. 事务四大特性 \quad ctrlaltt...

Highcharts 柱形图:深入解析与最佳实践

Highcharts 柱形图:深入解析与最佳实践 引言 Highcharts 是一个功能强大的图表库,它允许用户轻松地在网页上创建各种类型的图表。其中,柱形图因其直观的展示方式,在数据分析、业务报告等领域得到了广泛应用。本文将深入解析 Highcharts 柱形图,包括其基本用法、高级特性…...

js笔记(黑马程序员)

js&#xff08;day2&#xff09; 一、运算符 1.赋值运算符 运算符作用加法赋值-减法赋值*乘法复制/除法赋值%取余赋值 2.一元运算符 符号作用说明自增变量自身的值加1&#xff0c;如X--自减变量自身的值减1&#xff0c;如X-- 3.比较运算符 运算符作用>左边是否大于右…...

Mac m1,m2,m3芯片使用nvm安装node14报错

使用nvm安装了node 12/16/18都没有问题&#xff0c;到14就报错了。第一次看到这个报错有点懵&#xff0c;查询资料发现是Mac芯片的问题。 Issue上提供了两个方案&#xff1a; 1、为了在arm64的Mac上安装node 14&#xff0c;需要使用Rosseta&#xff0c;可以通过以下命令安装 …...

LeetCode:63. 不同路径 II

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;63. 不同路径 II 给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角&#xff08;即 grid[0][0]…...

安装zsh并美化

0 Zsh 是一种功能强大的 shell&#xff0c;通常用于替代默认的 Bash shell。它为命令行提供了更多的功能&#xff0c;例如自动补全、强大的模式匹配和主题支持等。 Oh My Zsh 是用于管理 Zsh 配置的框架。 powerlevel10k是样式&#xff0c;通过p10k configure脚本可以调节自己…...

读量子霸权18读后总结与感想兼导读

1. 基本信息 量子霸权 【美】加来道雄 著 中信出版集团股份有限公司,2024年4月出版 1.1. 读薄率 书籍总字数281千字&#xff0c;笔记总字数65977字。 读薄率65977281000≈23.48% 1.2. 读厚方向 量子宇宙 从掷骰子到阿尔法狗&#xff1a;趣谈概率 上帝掷骰子吗&#xf…...

统计学中的样本概率论中的样本

不知道当初谁想的把概率论和数理统计合并&#xff0c;作为一门课。这本身是可以合并&#xff0c;完整的一条线&#xff0c;看这里。但是&#xff0c;作为任课老师应该从整体上交代清楚&#xff0c;毕竟是两个学科&#xff0c;不同的学科合并必然会有各种不协调的问题。 举个最…...

HTML 符号详解

HTML 符号详解 引言 HTML(超文本标记语言)符号是HTML文档中用来表示特殊字符的标记。这些符号在日常网页设计和开发中扮演着重要角色,特别是在需要显示版权、商标、货币符号等特殊字符时。本文将详细介绍HTML符号的用法、类型以及如何在HTML文档中插入这些符号。 HTML符号…...

蓝桥杯练习日常|c/c++竞赛常用库函数(下)

书接上回......蓝桥杯算法日常|c\c常用竞赛函数总结备用-CSDN博客 目录 书接上回......https://blog.csdn.net/weixin_47011416/article/details/145290017 1、二分查找 2、lower_bound uper_bound 3、memset&#xff08;&#xff09; 函数原型 参数说明 返回值 常见用…...

Python vLLM 实战应用指南

文章目录 1. vLLM 简介2. 安装 vLLM3. 快速开始3.1 加载模型并生成文本3.2 参数说明 4. 实战应用场景4.1 构建聊天机器人示例对话&#xff1a; 4.2 文本补全输出示例&#xff1a; 4.3 自定义模型服务启动服务调用服务 5. 性能优化5.1 GPU 加速5.2 动态批处理 6. 总结 vLLM 是一…...

.NET MAUI 入门学习指南

引言 在当今移动应用和跨平台开发的热潮中,.NET MAUI(Multi - platform App UI)应运而生,为开发者提供了一种高效、统一的方式来构建跨多个平台(如 iOS、Android、Windows 等)的原生应用。它整合了 Xamarin.Forms 的优点,并在此基础上进行了诸多改进和创新,使得开发者…...

JavaScript系列(49)--游戏引擎实现详解

JavaScript游戏引擎实现详解 &#x1f3ae; 今天&#xff0c;让我们深入探讨JavaScript的游戏引擎实现。游戏引擎是一个复杂的系统&#xff0c;它需要处理渲染、物理、音频、输入等多个方面&#xff0c;让我们一步步实现一个基础但功能完整的游戏引擎。 游戏引擎基础概念 &am…...

AI如何帮助解决生活中的琐碎难题?

引言&#xff1a;AI已经融入我们的日常生活 你有没有遇到过这样的情况——早上匆忙出门却忘了带钥匙&#xff0c;到了公司才想起昨天的会议资料没有打印&#xff0c;或者下班回家还在纠结晚饭吃什么&#xff1f;这些看似微不足道的小事&#xff0c;往往让人疲惫不堪。而如今&a…...

K8s运维管理平台 - KubeSphere 3.x 和4.x 使用分析:功能较强,UI美观

目录标题 Lic使用感受优点&#xff1a;优化点&#xff1a; 实操首页项目 | 应用负载 | 配置 | 定制资源定义存储监控告警集群设置 **KubeSphere 3.x** 和 **4.x**1. **架构变化**&#xff1a;2. **多集群管理**&#xff1a;3. **增强的 DevOps 功能**&#xff1a;4. **监控与日…...

芯片AI深度实战:基础篇之langchain

基于ollama, langchain,可以构建一个自己的知识库&#xff0c;比如这个 Build Your Own RAG App: A Step-by-Step Guide to Setup LLM locally using Ollama, Python, and ChromaDB | HackerNoon 这是因为&#xff1a; 以上范例就实现了这样一个流程&#xff1a; 系列文章&…...

WordPress使用(1)

1. 概述 WordPress是一个开源博客框架&#xff0c;配合不同主题&#xff0c;可以有多种展现方式&#xff0c;博客、企业官网、CMS系统等&#xff0c;都可以很好的实现。 官网&#xff1a;博客工具、发布平台和内容管理系统 – WordPress.org China 简体中文&#xff0c;这里可…...

单机伪分布Hadoop详细配置

目录 1. 引言2. 配置单机Hadoop2.1 下载并解压JDK1.8、Hadoop3.3.62.2 配置环境变量2.3 验证JDK、Hadoop配置 3. 伪分布Hadoop3.1 配置ssh免密码登录3.2 配置伪分布Hadoop3.2.1 修改hadoop-env.sh3.2.2 修改core-site.xml3.2.3 修改hdfs-site.xml3.2.4 修改yarn-site.xml3.2.5 …...

【高内聚】设计模式是如何让软件更好做到高内聚的?

高内聚&#xff08;High Cohesion&#xff09;是指模块内部的元素紧密协作&#xff0c;共同完成一个明确且相对独立的功能。就像高效的小团队&#xff0c;成员们目标一致&#xff0c;相互配合默契。 低耦合&#xff08;Loose Coupling&#xff09;是指模块之间的依赖较少&#…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...