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

2023.10.22 关于 定时器(Timer) 详解

目录

引言

标准库定时器使用

自己实现定时器的代码

模拟实现的两大方面

核心思路

重点理解

 自己实现的定时器代码最终代码版本


引言

  • 定时器用于在 预定的时间间隔之后 执行特定的任务或操作

实例理解:

  • 在服务器开发中,客户端向服务器发送请求,等待服务器响应,但可能因为某一故障,导致程序一直无法响应,从而容易出现客户端卡死的情况,所以为了应对该情况,我们通常可以设置一个定时器,若未在规定的时间内完成任务,则可以做一些操作,来取消客户端的等待

标准库定时器使用

import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo24 {public static void main(String[] args) {System.out.println("程序启动");
//        这个 Timer 类就是标准库的定时器Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务A");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务B");}},4000);}
}
  • 上述代码中的 schedule 方法的效果是给定时器 注册一个任务,任务不会立即执行,而是在指定时间进行执行
  • 上述代码中的 schedule 方法有两个参数,一个参数为 TimerTask 接口,对 run 方法进行重写,从而指定要执行的任务,另一个参数为等待时间且单位为毫秒

注意:

  • 一个定时器可以同时安排多个任务
  • 定时器执行完任务之后,进程并不会立即退出,因为定时器内部需要维护一组线程来执行这些任务,这些线程被称为 前台线程
  • 当我创建一个定时器并安排任务时,定时器会启动一个或多个线程,这些线程负责按计划执行任务
  • 这些线程会一直运行,直到定时器被取消或程序显式地终止,这样做到目的是为了确保定时器能够准时执行任务,即使主线程已经完成或已退出
  • 由于这些前台线程在定时器内部运行,所以它们会影响进程的退出
  • 如果定时器中的任务尚未完成,这些前台线程将阻止进程退出,直到所有任务执行完毕或定时器被取消
  • 这确保了任务得到完整执行,并且程序能够正常结束
  • 从而需要注意的是,在使用定时器时不再需要它,我们应该主动取消定时器以释放资源并停止前台线程的执行
  • 这样可以避免不必要的资源占用和线程执行

自己实现定时器的代码

模拟实现的两大方面

  • 在指定时间执行所注册的任务
  • 一个定时器可注册多个任务,且这多个任务按照约定时间,顺序执行

核心思路

  • 有一个扫描线程,负责判定任务是否到执行时间
  • 需要一个 数据结构 来保存所有被注册的任务

注意:

  • 此处的每个任务均带有时间,并且一定是时间越靠前,就执行
  • 所以在当下的场景中使用 优先级队列 便是一个很好的选择
  • 时间小的,作为优先级高的
  • 此时队首元素 就是整个队列中 最先要执行的任务
  • 此时 扫描线程仅需扫描一下队首元素即可,不必遍历整个队列
  • 因为队首元素还没到执行时间,后续元素更不可能到执行时间
  • 当然 此处的优先级队列会在 多线程 环境下使用
  • 因为 调用 schedule 方法是一个线程,扫描是另一个线程,从而此处涉及到线程安全问题
  • 我们可以使用 标准库提供的 PriorityBlockingQueue ,阻塞队列本身就是线程安全的,所以 带优先级的阻塞队列 便十分切合我们的需求

  • 以下是一个自己实现的定时器
import java.util.concurrent.PriorityBlockingQueue;class MyTask implements Comparable<MyTask>{
//    要执行的任务内容private Runnable runnable;
//    任务在啥时候执行(使用毫秒时间戳表示)private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}
//    获取当前任务的时间public long getTime() {return time;}
//    执行任务时间public void  run() {runnable.run();}@Overridepublic int compareTo(MyTask o) {
//        当前想要实现队首元素是 时间最小的任务
//        这两是 谁减谁,不需要去记,往往可以试一试就知道了
//        要么就是 this.time - o.time, 要么就是 o.time - this.timereturn (int) (this.time - o.time);}
}class MyTimer {
//    扫描线程private Thread t = null;public MyTimer() {t = new Thread(() -> {while (true) {
//               取出队首元素,检查看看队首元素任务是否到时间了try {synchronized (this) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.getTime()) {
//                         如果时间还没到,就把任务塞回队列queue.put(myTask);
//                           在 put 之后,进行一个 wait 等待this.wait(myTask.getTime() - curTime);}else {
//                           如果时间到了,就把任务进行执行myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}//    有一个阻塞优先级队列,来保存任务private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//    指定两个参数
//    第一个参数是 任务 内容
//    第二个参数是 任务 在多少毫秒之后执行 如 1000public void schedule(Runnable runnable,long after) {
//        注意这里的时间换算,获取当前时间的时间戳加上需要等待的时间就是任务执行的时间MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);queue.put(task);synchronized (this) {this.notify();}}
}public class ThreadDemo25 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("执行了任务A");}},1000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("执行了任务B");}},2000);}
}

运行结果:


重点理解

  • MyTask 类是用来描述一个任务的
  • 其中包含 要执行的任务内容 和 任务在什么时候执行

  • 正因为我们使用 优先级阻塞队列 来保存我们所有被注册的任务
  • 所以我们需要指定当前任务的优先级是什么
  • 此处我们任务的优先级是 越早执行的任务其优先级越高,即队首元素是时间最小的任务
  • 从而我们需要实现一个 Comparable 接口,并重写 compareTo 方法  

注意:

  • 此处的变量 time 为 long 类型,所以需要强制类型转换为 int 类型
  • 而且此处到底是(this.time - o.time)还是(o.time - this.time),我们仅试试就知道了,毕竟就这两种减法,不要去死记硬背

  • 关于这里的时间戳

  • 在 Java 中,System.currentTimeMillis() 是一个静态方法,它返回自1970年1月1日午夜(格林威治时间)以来当前时间的毫秒数
  • 即返回一个 long类型的值,表示当前时间与1970年1月1日午夜之间的毫秒数差,这个值可以用来计算时间间隔、时间戳等操作
  • 类似于输出 1626379152345

  • 这里我们为什么要引入 wait 和 notify 呢?

  • queue.put 操作是从 queue 中取出首元素
  • 此时的 queue.put 操作是放在 while 循环中的,因为我们想保证任务能够及时执行,所以不断的循环取出我们 queue 中的首元素,拿出来与当前的时间进行比较,以免错过任务的执行时间
  • 但是我们会发现,当距离队首元素执行任务还有很长一段时间的时候,queue 也会快速循环地将队首元素取出、比较、放回,那此时很显然就是在 忙等,CPU 不停地执行该循环操作,却毫无意义
  • 那么我们便可以直接引入wait 和 notify 来解决此情形
  • 当 queue 取出队首元素进行比较时,如果发现其还未到执行时间,那么将再次把该元素放回到 queue 中,然后再 wait 阻塞等待当前时间与首元素执行时间的时间差
  • 正因为在 wait 阻塞等待的期间中,可能还会插入新元素,并且不能保证该新元素是否会成为 queue 中新的首元素,所以在我们每插入一个新元素时,都需要进行 notify 一次,唤醒线程,然后继续执行 while 循环中的操作
  • 所以此处不能简单的使用 sleep 进行阻塞等待,因为无法感知新元素插入所导致的新改变

  • 使用 wait 的前提是得拥有锁对象,所以要进行加锁操作
  • 那么此时的 synchronized 有以下两种加锁方式,哪种更好呢?

  • 我们拿 方案二的加锁方式 进行分析
  • 当 线程t 执行 queue.take 语句时,此时便会将 queue 的队首元素取出来,然后准备进行比较操作
  • 假设队首元素的执行时间为 11点,且此时的时间为 9点,即该队首元素还未到执行时间,那么便将会把该元素重新放回到 queue 中
  • 如果此时的 线程t 正准备要执行 wait 进行阻塞等待时,CPU 转而执行其他,也就是在还未 wait 的情况下,又新增了一个任务,并且此时该任务的执行时间为 10点
  • 那么在新增任务的前提下,继续执行 wait 阻塞等待 2个小时 ,此时便会直接错过准备在10点 执行的新任务
  • 造成上述情况最主要的原因就是方案二并没有保证 take 和 wait 这两个操作执行的原子性,导致在执行这两个操作之间,可能会 put 进一个新任务
  • 所以我们可以将 synchronized 加锁的范围扩大,直接将 锁的范围扩大到 方案一,以此想保证 take 和 wait 操作的原子性

  • 但是仅这样我们能解决上述问题嘛?
  • 也就是能否保证在执行 take 和 wait 这两个操作时,执行这两个操作之间,不会再 put 进一个新任务
  • 显然仅通过扩大上述 synchronized 加锁的范围扩大,并不能完全保证,我们得保证再对锁对象加锁时,其 put 方法也需放入到 同一个锁对象的锁中,即将使用方案二

  • 也就是当 扫描线程t 对 锁对象进行了加锁操作,此时其他线程便不能调用被  同一个 锁对象 加了锁的代码块
  • 具体来说就是当主线程调用 schedule 方法准备执行 queue.put 语句插入新任务时, 便因为 扫描线程t 未释放 锁对象,所以主线程不能获取到锁对象,从而便会阻塞在 锁外,从而只要当 扫描线程t 释放了锁对象,主线程才能获取到锁对象,也就才能执行 queue.put 语句,才能往 queue 队列中插入新任务
  • 所以通过以上修改,我们便能很好的保证在执行 take 和 wait 这两个操作时,执行这两个操作之间,不会再 put 进一个新任务

  • 仔细思考上述代码,我们还会发现问题
  • 那就是通过  synchronized 加锁的范围扩大 和 把 put 方法也放入到 同一个锁对象的锁中 这两个操作,虽然解决了出现下图所示情况

  • 但是经过上述调整,该代码会存在 死锁 的情况
  • 假设此时 new 了一个 MyTimer 对象定时器
  • 那么此时就会初始化并调用 MyTimer 构造方法,构造方法就会创建一个线程t1,并开始执行其 run 方法,此时 线程t 便会拿到锁对象,程序进入 run 方法,但是由于 queue 队列中没有元素,因此就会在 queue.take 处阻塞等待,直到有任务放入队列中
  • 此时主线程通过调用 schedule 方法准备往 queue 队列中加入任务,但是由于 线程t 已经拿到锁对象了,且并未释放锁对象,所以此时在准备执行 queue.put 语句时,便会阻塞等待,所以此时 schedule 无法将任务 put 到 queue 队列中,这时 线程t 在阻塞等待,schedule 也在阻塞等待,就出现了死锁

模拟代码示例

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class Syn {private Object locker = new Object();BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();public Syn() {Thread t1 = new Thread(() -> {synchronized (locker) {try {queue.take();} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();}public void say() throws InterruptedException {synchronized (locker) {queue.put(Integer.valueOf("1"));}}
}public class TestSyn {public static void main(String[] args) throws InterruptedException {Syn syn = new Syn();Thread.sleep(1);syn.say();System.out.println("主线程打印 queue 中的值:"+ syn.queue.take());}
}

执行结果:

  • 我们发现新插入到 queue 中的值并未打印到控制台

通过 jconsole 观察线程情况:

  • 综上所述,为了 防止死锁的发生,我们又需将 queue.put 操作放到 锁外
  • 与上文通过  synchronized 加锁的范围扩大 和 把 queue.put 操作也放入到 同一个锁对象的锁中 这两个操作来保证 take 和 wait 这两个操作执行的原子性,也就是在执行 take 和 wait 这两个操作之间,不会再 put 进一个新任务
  • 从而这里 queue.take 无论是放在锁外还是锁内,都会引发问题

 自己实现的定时器代码最终代码版本

  • 使用 优先级阻塞队列 无论如何修改代码总会存在 问题,所以 我们直接转而使用 优先普通级队列
  • 不再使用 自带阻塞效果的 take 和 put 方法了
import java.util.PriorityQueue;// 创建一个类, 用来描述定时器中的一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 任务啥时候执行. 毫秒级的时间戳.private long time;// 任务具体是啥.private Runnable runnable;public MyTimerTask(Runnable runnable, long delay) {// delay 是一个相对的时间差. 形如 3000 这样的数值.// 构造 time 要根据当前系统时间和 delay 进行构造.time = System.currentTimeMillis() + delay;this.runnable = runnable;}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}@Overridepublic int compareTo(MyTimerTask o) {// 认为时间小的, 优先级高. 最终时间最小的元素, 就会放到队首.// 怎么记忆, 这里是谁减去谁?? 不要记!! 记容易记错~~// 随便写一个顺序, 然后实验一下就行了.return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}// 定时器类的本体
class MyNewTimer {// 使用优先级队列, 来保存上述的 N 个任务private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 用来加锁的对象private Object locker = new Object();// 定时器的核心方法, 就是把要执行的任务添加到队列中.public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);// 每次来新的任务, 都唤醒一下之前的扫描线程. 好让扫描线程根据最新的任务情况, 重新规划等待时间.locker.notify();}}// MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了, 是否应该执行; 一方面当任务到点之后,// 就要调用这里的 Runnable 的 Run 方法来完成任务public MyNewTimer() {// 扫描线程Thread t = new Thread(() -> {while (true) {try {synchronized (locker) {while (queue.isEmpty()) {// 注意, 当前如果队列为空, 此时就不应该去取这里的元素.// 此处使用 wait 等待更合适. 如果使用 continue, 就会使这个线程 while 循环运行的飞快,// 也会陷入一个高频占用 cpu 的状态(忙等).locker.wait();}MyTimerTask task = queue.peek();long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 假设当前时间是 14:01, 任务时间是 14:00, 此时就意味着应该要执行这个任务了.// 需要执行任务.queue.poll();task.getRunnable().run();} else {// 让当前扫描线程休眠一下, 按照时间差来进行休眠.// Thread.sleep(task.getTime() - curTime);locker.wait(task.getTime() - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}// 写一个定时器
public class ThreadDemo27 {public static void main(String[] args) {MyNewTimer timer = new MyNewTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3");}}, 3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1");}}, 1000);System.out.println("程序开始运行");}
}

原因如下图所示:

相关文章:

2023.10.22 关于 定时器(Timer) 详解

目录 引言 标准库定时器使用 自己实现定时器的代码 模拟实现的两大方面 核心思路 重点理解 自己实现的定时器代码最终代码版本 引言 定时器用于在 预定的时间间隔之后 执行特定的任务或操作 实例理解&#xff1a; 在服务器开发中&#xff0c;客户端向服务器发送请求&#…...

【STM32】GPIO控制LED(寄存器版)

在开始之前记得先准备好环境&#xff1a; STM32F103核心板下载教程.pdf 林何/STM32F103C8 - 码云 - 开源中国 (gitee.com) 一、STM32的GPIO模块数据手册详解 每个GPIO端口对应16个引脚&#xff0c;例GPIOA&#xff08;PA0~PA15&#xff09;内核cpu就可以通过APB2总线对寄存器…...

Spring Boot OAuth 2.0整合—高级配置

一、概述 HttpSecurity.oauth2Login() 为定制OAuth 2.0登录提供了大量的配置选项。主要的配置选项被分组到它们的协议端点对应处。 例如&#xff0c;oauth2Login().authorizationEndpoint() 允许配置授权端点&#xff0c;而 oauth2Login().tokenEndpoint() 允许配置令牌端点。…...

软考-虚拟专用网原理与应用

本文为作者学习文章&#xff0c;按作者习惯写成&#xff0c;如有错误或需要追加内容请留言&#xff08;不喜勿喷&#xff09; 本文为追加文章&#xff0c;后期慢慢追加 by 2023年10月 虚拟专用网概念 虚拟专用网&#xff08;Virtual Private Network&#xff09;是一种通过…...

clock_property 时钟的常用属性

get_property [get_clocks] property_option 1. period get_property [get_clocks] period 查询所有clock 的周期&#xff0c;如果存在loops会生成CTE_loops.rpt 2.clock_network_pins 查询clock所有的pins 3.generated_clocks_extended 查询clock分频产生的generate…...

平板有必要买触控笔吗?推荐的ipad手写笔

iPad之所以能吸引这么多人&#xff0c;主要是因为它的功能出色。用来画画、做笔记&#xff0c;也是一种不错的体验。但如果只是用来看电视和打游戏的话&#xff0c;那就真的有点大材小用了。如果你不需要昂贵的苹果电容笔&#xff0c;也不需要用来专业的绘图&#xff0c;那你可…...

Qt扫描-QMoive 理论总结

QMoive 理论总结 一、概述二、使用1. 使用2. 信号发出时机 三、控制的相关槽函数四、信号 一、概述 QMovie类是一个使用QImageReader播放 动画 的方便类。这个类用于显示没有声音的简单动画&#xff0c;一般即是 gif 动画。如果要显示视频和媒体内容&#xff0c;请使用Qt Mult…...

类似东郊到家预约家政保洁小程序搭建

随着生活水平的提高&#xff0c;人们对健康养生的需求越来越重视&#xff0c;按摩作为一种传统的养生方式&#xff0c;备受关注。为了方便用户快速、方便地预约按摩服务&#xff0c;本文将介绍一款按摩预约小程序的开发。 首先&#xff0c;我们通过市场调研和分析发现&#xf…...

[补题记录] Atcoder Beginner Contest 325(E、F)

URL&#xff1a;https://atcoder.jp/contests/abc325 目录 E Problem/题意 Thought/思路 Code/代码 F Problem/题意 Thought/思路 Code/代码 E Problem/题意 有一个二维矩阵&#xff0c;D[i][j] 表示从 i 到 j 的距离。从 i 到 j 有两种方式&#xff1a; 坐汽车&…...

1024啊啊啊啊啊啊

1024 程序员节快乐&#xff0c;没什么想发的&#xff0c;只是想要个1024胸章。...

淘宝商品详情API接口(H5端和APP端),淘宝详情页,商品属性接口,商品信息查询

一、接口参数说明&#xff1a;提取淘宝商品详情页各项数据&#xff0c;包含skuid、价格、收藏数、加购数、月销售量、主图、标题、详情页图片等页面上可以看奥的数据都可以拿到。 点击获取key和secret 二、使用场景 1、商品销售情况分析&#xff0c;根据销量调整活动方案&am…...

JVM的几个面试重点

JVM的内存区域划分 JVM类加载机制 前言 Java程序最开始是一个 .java 的文件&#xff0c;JVM把它编译成 .closs 文件&#xff08;字节码文件&#xff09;&#xff0c;运行 Java 程序&#xff0c; JVM 就会读取 .class 文件&#xff0c;把文件内容读取到内存中&#xff0c;构造出…...

[yolo系列:YOLOV7改进-添加CoordConv,SAConv.]

文章目录 概要CoordConvSAConv 概要 CoordConv&#xff08;Coordinate Convolution&#xff09;和SAConv&#xff08;Spatial Attention Convolution&#xff09;是两种用于神经网络中的特殊卷积操作&#xff0c;用于处理图像数据或其他多维数据。以下是它们的简要介绍&#x…...

【万字实操】可视化运维平台openGauss Datakit,带你轻松玩转openGauss 5.0

openGauss Datakit:openGauss社区推出的可视化的运维工具. 特性优势 初级用户学习openGauss门槛高让你望而却步&#xff1f;openGauss Datakit一键化安装企业版集群、监控、日志分析、SQL诊断&#xff0c;让你快速上手&#xff0c;快速部署&#xff0c;从容面对企业环境&#…...

《动手学深度学习 Pytorch版》 10.1 注意力提示

10.1.1 生物学中的注意力提示 “美国心理学之父” 威廉詹姆斯提出的双组件&#xff08;two-component&#xff09;框架&#xff1a; 非自主性提示&#xff1a;基于环境中物体的突出性和易见性 自主性提示&#xff1a;受到了认知和意识的控制 10.1.2 查询、键和值 注意力机制…...

C# 写入文件比较

数据长度&#xff1a;128188个long BinaryWriter每次写一个long 耗时14.7828ms StreamWriter每次写一个long 耗时44.0934 ms FileStream每次写一个long 耗时20.5142 ms FileStream固定chunk写入&#xff0c;循环操作数组&#xff0c;耗时13.4126 ms byte[] chunk new byte[d…...

医院设备利用(Use of Hospital Facilities, ACM/ICPC World Finals 1991, UVa212)rust解法

医院里有n&#xff08;n≤10&#xff09;个手术室和m&#xff08;m≤30&#xff09;个恢复室。每个病人首先会被分配到一个手术室&#xff0c;手术后会被分配到一个恢复室。从任意手术室到任意恢复室的时间均为t1&#xff0c;准备一个手术室和恢复室的时间分别为t2和t3&#xf…...

解决github ping不通的问题(1024程序员节快乐!

1024程序员节快乐&#xff01;&#xff08;随便粘贴一个文档&#xff0c;参加活动 解决github ping不通的问题 域名解析&#xff08;域名->IP&#xff09;&#xff1a;https://www.ipaddress.com/ Ubuntu平台 github经常ping不通或者访问缓慢&#xff0c;方法是更改host…...

QT基础 柱状图

目录 1.QBarSeries 2.QHorizontalBarSeries 3.QPercentBarSeries 4.QHorizontalPercentBarSeries 5.QStackedBarSeries 6.QHorizontalStackedBarSeries 从上图得知柱状的基类是QAbstractBarSeries&#xff0c;派生出来分别是柱状图的水平和垂直类&#xff0c;只是类型…...

微机原理与接口技术-第七章输入输出接口

文章目录 I/O接口概述I/O接口的典型结构基本功能 I/O端口的编址独立编址统一编址 输入输出指令I/O寻址方式I/O数据传输量I/O保护 16位DOS应用程序DOS平台的源程序框架DOS功能调用 无条件传送和查询传送无条件传送三态缓冲器锁存器接口电路 查询传送查询输入端口查询输出端口 中…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...