SchedulingConfigurer使用教程

SchedulingConfigurer使用教程:Java定时任务的高阶使用

在 Java 开发中,定时任务的管理和执行是一个常见需求。Spring 提供了多种方式来处理定时任务,其中 SchedulingConfigurer 是一个强大且灵活的接口,允许我们对定时任务进行更高级的配置和管理。本文将深入探讨如何使用 SchedulingConfigurer 实现复杂的定时任务调度。

1. 什么是 SchedulingConfigurer

SchedulingConfigurer 是 Spring Framework 提供的一个接口,用于配置任务调度。它允许我们自定义任务调度器的行为,例如配置线程池、定义任务调度规则等。

2. 配置 SchedulingConfigurer

首先,我们需要实现 SchedulingConfigurer 接口,并重写 configureTasks 方法。在这个方法中,我们可以使用 ScheduledTaskRegistrar 来注册我们的定时任务。

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;@Configuration
@EnableScheduling
public class CustomSchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {// 这里可以添加自定义的任务调度逻辑}
}

3. 配置线程池

为了更好地管理和优化定时任务的执行,我们通常需要配置一个线程池。通过 ScheduledTaskRegistrar,我们可以很方便地设置一个自定义的线程池。

import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;@Configuration
@EnableScheduling
public class CustomSchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(10);taskScheduler.setThreadNamePrefix("Scheduled-Task-");taskScheduler.initialize();taskRegistrar.setTaskScheduler(taskScheduler);}
}

在上面的代码中,我们创建了一个 ThreadPoolTaskScheduler,并设置了线程池的大小和线程名称的前缀。然后,我们将这个任务调度器注册到 ScheduledTaskRegistrar 中。

4. 注册定时任务

configureTasks 方法中,我们可以使用 ScheduledTaskRegistrar 来注册我们的定时任务。我们可以使用 Cron 表达式来定义任务的执行时间。

import org.springframework.scheduling.support.CronTrigger;@Configuration
@EnableScheduling
public class CustomSchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(10);taskScheduler.setThreadNamePrefix("Scheduled-Task-");taskScheduler.initialize();taskRegistrar.setTaskScheduler(taskScheduler);// 注册定时任务taskRegistrar.addTriggerTask(() -> System.out.println("定时任务执行: " + System.currentTimeMillis()),new CronTrigger("0/5 * * * * ?"));}
}

在上面的代码中,我们使用 addTriggerTask 方法注册了一个每 5 秒执行一次的定时任务。任务的逻辑可以是任何符合 Runnable 接口的代码。

5. 动态调整定时任务

有时候,我们需要在运行时动态调整定时任务的调度规则。我们可以通过维护一个共享变量来实现这一点。

import java.util.concurrent.atomic.AtomicReference;@Configuration
@EnableScheduling
public class CustomSchedulingConfigurer implements SchedulingConfigurer {private final AtomicReference<String> cronExpression = new AtomicReference<>("0/5 * * * * ?");@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(10);taskScheduler.setThreadNamePrefix("Scheduled-Task-");taskScheduler.initialize();taskRegistrar.setTaskScheduler(taskScheduler);// 注册定时任务taskRegistrar.addTriggerTask(() -> System.out.println("定时任务执行: " + System.currentTimeMillis()),triggerContext -> {String cron = cronExpression.get();return new CronTrigger(cron).nextExecutionTime(triggerContext);});}// 动态修改 cron 表达式public void updateCronExpression(String newCron) {cronExpression.set(newCron);}
}

在上面的代码中,我们使用 AtomicReference 来保存 cron 表达式,并在 addTriggerTask 方法中动态获取最新的 cron 表达式。通过调用 updateCronExpression 方法,我们可以在运行时更新定时任务的执行规则。

6. 使用自定义的 Trigger 实现复杂的调度规则

除了使用 CronTrigger,我们还可以创建自定义的 Trigger 实现复杂的调度规则。例如,我们可以根据特定的业务逻辑动态计算下次执行时间。

import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;import java.util.Date;@Configuration
@EnableScheduling
public class CustomSchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(10);taskScheduler.setThreadNamePrefix("Scheduled-Task-");taskScheduler.initialize();taskRegistrar.setTaskScheduler(taskScheduler);// 注册定时任务taskRegistrar.addTriggerTask(() -> System.out.println("定时任务执行: " + System.currentTimeMillis()),new CustomTrigger());}private static class CustomTrigger implements Trigger {@Overridepublic Date nextExecutionTime(TriggerContext triggerContext) {// 自定义计算下一次执行时间的逻辑return new Date(System.currentTimeMillis() + 5000);}}
}

在上面的代码中,CustomTrigger 实现了 Trigger 接口,并在 nextExecutionTime 方法中自定义了下一次执行时间的计算逻辑。

7. 多任务调度和任务优先级

如果你的应用程序中有多个定时任务,并且这些任务有不同的优先级或需要不同的调度器,我们可以为每个任务配置不同的 TaskScheduler

@Configuration
@EnableScheduling
public class MultiTaskSchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {// 配置任务1的调度器ThreadPoolTaskScheduler taskScheduler1 = new ThreadPoolTaskScheduler();taskScheduler1.setPoolSize(5);taskScheduler1.setThreadNamePrefix("Task1-");taskScheduler1.initialize();taskRegistrar.setTaskScheduler(taskScheduler1);taskRegistrar.addTriggerTask(() -> System.out.println("任务1执行: " + System.currentTimeMillis()),new CronTrigger("0/10 * * * * ?"));// 配置任务2的调度器ThreadPoolTaskScheduler taskScheduler2 = new ThreadPoolTaskScheduler();taskScheduler2.setPoolSize(3);taskScheduler2.setThreadNamePrefix("Task2-");taskScheduler2.initialize();taskRegistrar.addTriggerTask(() -> System.out.println("任务2执行: " + System.currentTimeMillis()),new CronTrigger("0/15 * * * * ?"));}
}

在上面的代码中,我们为两个任务配置了不同的 TaskScheduler,并且设置了不同的线程池和调度规则。

8. 动态添加和移除任务

在实际应用中,我们可能需要动态地添加和移除任务。我们可以通过维护一个任务列表来实现这一功能。

import java.util.ArrayList;
import java.util.List;@Configuration
@EnableScheduling
public class DynamicGroupedTaskSchedulingConfigurer implements SchedulingConfigurer {private final Map<TaskGroup, ThreadPoolTaskScheduler> taskSchedulers = new HashMap<>();private final Map<TaskGroup, List<ScheduledFuture<?>>> scheduledTasks = new HashMap<>();@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {configureTaskScheduler(TaskGroup.GROUP_A, 5, "GroupA-Task-");configureTaskScheduler(TaskGroup.GROUP_B, 3, "GroupB-Task-");}private void configureTaskScheduler(TaskGroup group, int poolSize, String threadNamePrefix) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(poolSize);taskScheduler.setThreadNamePrefix(threadNamePrefix);taskScheduler.initialize();taskSchedulers.put(group, taskScheduler);scheduledTasks.put(group, new ArrayList<>());}public void addTask(TaskGroup group, Runnable task, String cronExpression) {ThreadPoolTaskScheduler scheduler = taskSchedulers.get(group);if (scheduler != null) {ScheduledFuture<?> future = scheduler.schedule(task, new CronTrigger(cronExpression));scheduledTasks.get(group).add(future);}}public void removeAllTasks(TaskGroup group) {List<ScheduledFuture<?>> tasks = scheduledTasks.get(group);if (tasks != null) {for (ScheduledFuture<?> task : tasks) {task.cancel(true);}tasks.clear();}}
}

在上面的代码中,我们维护了一个任务列表 scheduledTasks,并提供了 addTaskremoveAllTasks 方法来动态地添加和移除任务。

示例使用

我们可以通过控制器或服务类来管理任务的添加和移除。

import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/tasks")
public class TaskController {private final DynamicGroupedTaskSchedulingConfigurer taskConfigurer;public TaskController(DynamicGroupedTaskSchedulingConfigurer taskConfigurer) {this.taskConfigurer = taskConfigurer;}@PostMapping("/add")public String addTask(@RequestParam TaskGroup group, @RequestParam String cron) {taskConfigurer.addTask(group, () -> System.out.println("动态任务执行: " + System.currentTimeMillis()), cron);return "任务已添加";}@DeleteMapping("/removeAll")public String removeAllTasks(@RequestParam TaskGroup group) {taskConfigurer.removeAllTasks(group);return "所有任务已移除";}
}

通过上述控制器,我们可以动态地添加和移除任务组中的任务。例如,通过以下请求可以添加和移除任务:

  • 添加任务:POST /tasks/add?group=GROUP_A&cron=0/5 * * * * ?
  • 移除任务:DELETE /tasks/removeAll?group=GROUP_A

9. 错误处理和重试机制

在某些情况下,定时任务可能会失败。为了提高任务的可靠性,我们可以为定时任务添加错误处理和重试机制。

import org.springframework.scheduling.TriggerContext;@Configuration
@EnableScheduling
public class ErrorHandlingSchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(10);taskScheduler.setThreadNamePrefix("ErrorHandling-Task-");taskScheduler.initialize();taskRegistrar.setTaskScheduler(taskScheduler);// 注册带错误处理和重试机制的定时任务taskRegistrar.addTriggerTask(() -> {try {System.out.println("任务执行: " + System.currentTimeMillis());// 模拟任务执行逻辑if (Math.random() > 0.7) {throw new RuntimeException("模拟任务执行失败");}} catch (Exception e) {System.err.println("任务执行失败: " + e.getMessage());// 添加重试逻辑,例如延迟重试taskScheduler.schedule(this::retryTask, new CronTrigger("0/10 * * * * ?"));}},new CronTrigger("0/5 * * * * ?"));}private void retryTask() {System.out.println("重试任务执行: " + System.currentTimeMillis());}
}

在上面的代码中,我们在任务执行逻辑中添加了错误处理逻辑,并在任务失败时使用 taskScheduler 调度重试任务。

10. 任务分组

任务分组可以帮助我们更好地组织和管理定时任务。我们可以为不同的任务分组设置不同的调度器和调度规则,从而实现更加灵活的任务管理。

10.1 定义任务组

我们可以通过一个枚举类来定义任务组,并在配置类中使用这些任务组来组织任务。

public enum TaskGroup {GROUP_A,GROUP_B
}

10.2 配置任务组调度器

为每个任务组配置不同的 TaskScheduler 和调度规则。

@Configuration
@EnableScheduling
public class GroupedTaskSchedulingConfigurer implements SchedulingConfigurer {private final Map<TaskGroup, ThreadPoolTaskScheduler> taskSchedulers = new HashMap<>();@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {configureTaskScheduler(TaskGroup.GROUP_A, 5, "GroupA-Task-");configureTaskScheduler(TaskGroup.GROUP_B, 3, "GroupB-Task-");// 注册任务组A的定时任务taskRegistrar.setTaskScheduler(taskSchedulers.get(TaskGroup.GROUP_A));taskRegistrar.addTriggerTask(() -> System.out.println("任务组A-任务1执行: " + System.currentTimeMillis()),new CronTrigger("0/10 * * * * ?"));// 注册任务组B的定时任务taskRegistrar.setTaskScheduler(taskSchedulers.get(TaskGroup.GROUP_B));taskRegistrar.addTriggerTask(() -> System.out.println("任务组B-任务1执行: " + System.currentTimeMillis()),new CronTrigger("0/15 * * * * ?"));}private void configureTaskScheduler(TaskGroup group, int poolSize, String threadNamePrefix) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(poolSize);taskScheduler.setThreadNamePrefix(threadNamePrefix);taskScheduler.initialize();taskSchedulers.put(group, taskScheduler);}
}

在上面的代码中,我们为每个任务组配置了不同的 TaskScheduler,并在 configureTasks 方法中注册了不同的定时任务。

10.3 动态添加和移除任务

在实际应用中,我们可能需要动态地添加和移除任务。我们可以通过维护一个任务列表来实现这一功能。

import java.util.ArrayList;
import java.util.List;@Configuration
@EnableScheduling
public class DynamicGroupedTaskSchedulingConfigurer implements SchedulingConfigurer {private final Map<TaskGroup, ThreadPoolTaskScheduler> taskSchedulers = new HashMap<>();private final Map<TaskGroup, List<ScheduledFuture<?>>> scheduledTasks = new HashMap<>();@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {configureTaskScheduler(TaskGroup.GROUP_A, 5, "GroupA-Task-");configureTaskScheduler(TaskGroup.GROUP_B, 3, "GroupB-Task-");}private void configureTaskScheduler(TaskGroup group, int poolSize, String threadNamePrefix) {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(poolSize);taskScheduler.setThreadNamePrefix(threadNamePrefix);taskScheduler.initialize();taskSchedulers.put(group, taskScheduler);scheduledTasks.put(group, new ArrayList<>());}public void addTask(TaskGroup group, Runnable task, String cronExpression) {ThreadPoolTaskScheduler scheduler = taskSchedulers.get(group);if (scheduler != null) {ScheduledFuture<?> future = scheduler.schedule(task, new CronTrigger(cronExpression));scheduledTasks.get(group).add(future);}}public void removeAllTasks(TaskGroup group) {List<ScheduledFuture<?>> tasks = scheduledTasks.get(group);if (tasks != null) {for (ScheduledFuture<?> task : tasks) {task.cancel(true);}tasks.clear();}}
}

在上面的代码中,我们维护了一个任务列表 scheduledTasks,并提供了 addTaskremoveAllTasks 方法来动态地添加和移除任务。

示例使用

我们可以通过控制器或服务类来管理任务的添加和移除。

import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/tasks")
public class TaskController {private final DynamicGroupedTaskSchedulingConfigurer taskConfigurer;public TaskController(DynamicGroupedTaskSchedulingConfigurer taskConfigurer) {this.taskConfigurer = taskConfigurer;}@PostMapping("/add")public String addTask(@RequestParam TaskGroup group, @RequestParam String cron) {taskConfigurer.addTask(group, () -> System.out.println("动态任务执行: " + System.currentTimeMillis()), cron);return "任务已添加";}@DeleteMapping("/removeAll")public String removeAllTasks(@RequestParam TaskGroup group) {taskConfigurer.removeAllTasks(group);return "所有任务已移除";}
}

通过上述控制器,我们可以动态地添加和移除任务组中的任务。例如,通过以下请求可以添加和移除任务:

  • 添加任务:POST /tasks/add?group=GROUP_A&cron=0/5 * * * * ?
  • 移除任务:DELETE /tasks/removeAll?group=GROUP_A

最佳实践

  1. 使用线程池优化性能:使用线程池可以避免线程资源耗尽,提升任务调度的性能和稳定性。
  2. 定期检查和调整任务调度:根据业务需求和系统性能,定期检查和调整定时任务的调度策略。
  3. 日志记录和监控:为定时任务添加日志记录和监控,方便排查问题和优化性能。
  4. 异常处理和重试机制:为关键任务添加异常处理和重试机制,确保任务的可靠执行。
  5. 分组管理任务:将相关任务进行分组管理,提高任务调度的灵活性和可维护性。

注意事项

  1. 避免任务堆积:确保任务执行时间短于调度周期,避免任务堆积导致系统性能下降。
  2. 合理设置线程池大小:根据实际业务需求和服务器性能,合理设置线程池的大小,避免线程资源耗尽或浪费。
  3. 避免重复调度:确保同一任务不会被重复调度,避免任务逻辑被多次执行。
  4. 监控系统资源:定时任务可能会消耗大量系统资源,需监控系统资源使用情况,防止资源耗尽。
  5. 优雅停机:在应用停机时,确保正在执行的任务能优雅终止或完成,避免数据不一致或任务中断。

通过遵循这些最佳实践和注意事项,你可以更加高效地管理和优化定时任务,确保系统的稳定性和可靠性。

结论

通过以上高级技巧,我们可以更加灵活和高效地管理定时任务。无论是自定义调度规则、多任务调度、动态启停任务,还是错误处理和重试机制,SchedulingConfigurer 都提供了丰富的扩展点。希望这些高级使用场景能够帮助你在实际项目中更好地应用定时任务调度。
如果你有其他高级使用技巧或遇到任何问题,欢迎在评论区分享和讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/41886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

说一下浏览器中的强缓存和协商缓存的区别

写在前面 对于一道高频的面试题&#xff0c;可能很多小伙伴还不知道这两者的概念&#xff0c;不知道是用来做什么的&#xff0c;以及有什么好处&#xff0c;强缓存和协商缓存是 Web 缓存机制的重要组成部分&#xff0c;它们在优化 Web 应用性能方面发挥了重要作用&#xff0c;…

Vue + SpringBoot:el-upload组件单文件、多文件上传实战解析

文章目录 单文件上传后端前端 多文件上传后端前端 单文件上传 后端 PostMapping("/uploadDxfFile") public R uploadDxfFile(RequestParam(value "file", required true) MultipartFile multipartFile) throws Exception {// 文件校验工作if (multipar…

web Worker学习笔记 | 浏览器切换标签,定时器失效的解决办法

文章目录 web Workerweb Worker介绍 - 多线程解决方案浏览器多进程架构 web workers 的使用关闭worker引用其他js文件 浏览器切换标签&#xff0c;定时器失效的解决办法窗口可见性 API解决定时器失效的方案 web Worker web Worker介绍 - 多线程解决方案 Web Workers 是Html5提…

服务器数据恢复—DS5300存储raid5阵列数据恢复案例

服务器存储数据恢复环境&#xff1a; 某单位一台某品牌DS5300存储&#xff0c;1个机头4个扩展柜&#xff0c;50块硬盘组建2组RAID5磁盘阵列&#xff08;一组raid5阵列有27块成员盘&#xff0c;存放Oracle数据库文件&#xff1b;另外一组raid5阵列有23块成员盘&#xff09;。存储…

大带宽独立服务器的购买和配置流程是怎样的?

在当前互联网时代&#xff0c;大带宽独立服务器越来越受到企业和个人用户的青睐。其稳定性和高速的数据传输速率可以为用户提供更好的互联网体验。下面我们将详细介绍如何购买和配置大带宽独立服务器。 第一步&#xff1a;选择合适的服务器提供商 在选择服务器提供商时&#…

CF1982D Beauty of the mountains

【题意】 Nikita 喜欢爬山。当地的山可以看作一个由 ( n m ) (n \times m) (nm) 个格子组成的 n m n \times m nm 的矩形&#xff0c;每一个格子都有一个初始非负高度 a i , j a_{i,j} ai,j​。 格子有两种类型&#xff1a; 有雪无雪 Nikita 有一种超能力&#xff1a;…

数组相关内容

一、数组 就是一个集合&#xff0c;里面存放了相同类型的数据元素 特点&#xff1a; 1.数组中的每个数据元素都是相同的数据类型 2.数组是由连续内存位置组成的 二、一维数组 定义方式 1.数据类型 数组名[数组长度]&#xff1b; 2.数据类型 数组名[数组长度]{值1&#xff0…

Jacoco的覆盖率原理

收集覆盖率信息的方法 Runtime Profiling Runtime Profiling是一种在程序运行时进行的性能分析技术,它可以帮助开发者了解程序的运行情况,识别性能瓶颈和优化程序性能。由于是在程序运行时进行,runtime profiling 能够提供实时的数据,便于理解程序在实际运行条件下的行为…

周报7.1-7.7

学习内容 了解注意力机制的相关内容&#xff0c;学习注意力提示、注意力汇聚、评分函数、Bahdanau注意力、多头注意力、Transformer等内容。了解Bahdanau注意力和Transformer的模型代码实现。Qt的碳汇源继续需求整改 下周计划 学习Pytorch的代码实战&#xff0c;学习使用Ten…

HashMap中的put()方法

一. HashMap底层结构 HashMap底层是由哈希表(数组),链表,红黑树构成,哈希表存储的类型是一个节点类型,哈希表默认长度为16,它不会每个位置都用,当哈希表中的元素个数大于等于负载因子(0.75)*哈希表长度就会扩容到原来的2倍 二. 底层的一些常量 三. HashMap的put()方法 当插入一…

ONLYOFFICE最新8.1版本——桌面编辑器简单测评

前言 大家好&#xff0c;我是小雨&#xff0c;看到最近ONLYOFFICE更新了最新的版本&#xff0c;更新了一下当前版本来具体的测评一下&#xff0c;先来看看官网提供的各类更新信息&#xff0c;下面是我找到的三个主页&#xff0c;包括功能演示链接&#xff0c;官网连接以及专门…

阿里云存储

传统存储面临的挑战 现如今&#xff0c;数据与土地、劳动力、资本、技术并称为五大生产要素&#xff0c;数据成为数字经济发展的重要引擎。大数据时代&#xff0c;数据处理的需求急剧增长&#xff0c;越来越多的智能设备以及新应用如区块链、机器学习训练和AI等产生大量的数据…

复分析——第10章——Θ函数应用(E.M. Stein R. Shakarchi)

第10章 Θ函数的应用 (Applications of Theta Functions) The problem of the representation of an integer n as the sum of a given number k of integral squares is one of the most celebrated in the theory of numbers. Its history may be traced back to Diopha…

CV03_mAP计算以及COCO评价标准

COCO数据集回顾&#xff1a;CV02_超强数据集&#xff1a;MSCOCO数据集的简单介绍-CSDN博客 1.1 简介 在目标检测领域中&#xff0c;mAP&#xff08;mean Average Precision&#xff0c;平均精度均值&#xff09;是一个广泛使用的性能评估指标&#xff0c;用于衡量目标检测模型…

短信验证码实现

一、设置AccessKey 创建用户并配置使用权限&#xff0c;使我们拥有调用 aliyunAPI 的权限&#xff0c;之后会生成 AccessKeyID 和 AccessKey密码&#xff0c;后面我们会使用到。需要注意的是 AccessKeyID 和 AccessKey密码生成后我们需要将他保存起来&#xff0c;否则后期无法查…

奇迹MU 骷髅战士在哪

BOSS分布图介绍 我为大家带来各地区怪物分布图。在游戏前期&#xff0c;很多玩家可能会不知道该去哪里寻找怪物&#xff0c;也不知道哪些怪物值得打。如果选择了太强的怪物&#xff0c;弱小的玩家可能会无法抵御攻击。如果选择了低等级的boss&#xff0c;收益可能并不理想。所…

智能家居安防系统教学解决方案

前言 随着科技的不断进步和智能家居概念的深入人心&#xff0c;智能家居安防系统作为智能家居领域的重要组成部分&#xff0c;其重要性日益凸显。智能家居安防系统不仅能够提供环境和人员的监测功能&#xff0c;还能够采取措施降低或避免人员伤亡及财产损失。因此&#xff0c;…

Double 4 VR虚拟情景智能互动系统在高铁乘务管理课堂上的应用

在不断进步的科技背景下&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已广泛应用于教育领域。VR技术的运用为课堂教育提供了新的思路与方式&#xff0c;尤其在高铁乘务管理这一专业中&#xff0c;Double 4 VR虚拟情景智能互动系统更是发挥了其独特的优势。 一、VR技术带…

word文档没有保存就关闭了怎么恢复?找到正确的方法

昨天写教程的时候&#xff0c;终于完成了一个word文档&#xff0c;以为保存了就直接关了。word提醒我“是否保存”&#xff0c;我直接忽略了。动作一气呵成&#xff0c;毫不犹豫的关闭了。之后才发现我没有保存word文档。这种情况大家有遇到过吗?我们该如何在没有保存的情况下…

迎接AI新时代:GPT-5即将登场的巨大变革与应用前瞻

迎接AI新时代&#xff1a;GPT-5即将登场的巨大变革与应用前瞻 &#x1f48e;1. GPT-5 一年半后发布&#xff1a;AI新时代的来临1.1 GPT-5的飞跃&#xff1a;从高中生到博士生 &#x1f48e;2. GPT-5的潜在应用场景&#x1f48e;2.1 医疗诊断和健康管理&#x1f48e;2.2 教育领域…