定时任务:从Timer、STPE、Quartz 到 XXL-JOB

news2025/1/24 14:28:16
  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor 简称STPE
  • Quartz
  • XXL-JOB

基本套路

定时任务基本上都是在一个while(true)for(;;)死循环中(每次循环判断定时程序是否终止或暂停),从任务存放的地(可以是内存的堆结构,可以是远程数据库获取,可以是阻塞队列)获取最近要执行的任务,获取的最近的任务没到时间就阻塞一段时间,阻塞可以用Object.waitCondition::awaitNanos。对于周期执行的任务,每次执行完毕将下一个周期的自身再次加入任务存放的地方。
除此之外,一个完善的定时框架要考虑执行线程池、如何处理过期任务(顺序执行?丢弃?覆盖?)、重试策略、监听器等方面问题。分布式定时任务还涉及到rpc、任务的分片、执行器的负载均衡执行策略等问题。

Timer

案例:

public static void main(String[] args) {
		// daemon
        Timer timer = new Timer(true);
		// 每秒执行一次
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("timertask running at "+ new Date());
            }
        },0,1000);

        Thread.sleep(10000);
}

源码

Corresponding to each Timer object is a single background thread that is used to execute all of the timer’s tasks, sequentially. If a timer task takes excessive time to complete, it “hogs” the timer’s task execution thread. This can, in turn, delay the execution of subsequent tasks, which may “bunch up” and execute in rapid succession when (and if) the offending task finally completes.
This class is thread-safe: multiple threads can share a single Timer object without the need for external synchronization.
This class does not offer real-time guarantees: it schedules tasks using the Object.wait(long) method.
Java 5.0 introduced ScheduledThreadPoolExecutor which is a versatile replacement for the Timer/TimerTask combination, as it allows multiple service threads, accepts various time units, and doesn’t require subclassing TimerTask (just implement Runnable).
Internally, it uses a binary heap to represent its task queue, so the cost to schedule a task is O(log n), where n is the number of concurrently scheduled tasks.

可以看出,一个Timer中只有一个线程里执行调度任务,所以一个任务执行时间过长会影响后续任务,使用wait方法调度,任务放在一个二叉堆中。不推荐使用。

Timer中包含一个执行线程和一个任务队列:

// 根据nextExecutionTime排序的堆
private final TaskQueue queue = new TaskQueue();
// 传入queue执行
private final TimerThread thread = new TimerThread(queue);

定时任务使用TimerTask代替,TimerTask包含4种状态以及一个period来代表执行策略:

static final int VIRGIN = 0;
static final int SCHEDULED   = 1;
static final int EXECUTED    = 2;
static final int CANCELLED   = 3;

// positive value : fixed-rate execution
// negative value : fixed-delay execution
// 0 : non-repeating task
long period = 0;

来看看TimerThread如何安排 TaskQueue中的TimerTask

class TimerThread extends Thread {
	// 代表是否还有任务,没有会优雅终止调度
	boolean newTasksMayBeScheduled = true;

	private TaskQueue queue;

	public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }
	private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // 没有任务且newTasksMayBeScheduled为true就挂起
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    // 最近的任务
                    task = queue.getMin();
                    synchronized(task.lock) {
                    	// 是否取消
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        // 设置是否需要执行标志
                        if (taskFired = (executionTime<=currentTime)) {
                            // 只定时就移除
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                // 定时重复执行则调整到该任务下一次执行时间,queue会重新堆化
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    // 没超时就wait一些时间
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                // 判断执行
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }


}


可以看到Timer是获取最近时间的定时任务,如果没到时间线程会挂起executionTime - currentTime时间。

ScheduledThreadPoolExecutor (STPE)

案例

	public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(3);
        executor.scheduleAtFixedRate(() -> System.out.println("running at "+ new Date()),0,1, TimeUnit.SECONDS);
    }

简介

This class is preferable to Timer when multiple worker threads are needed, or when the additional flexibility or capabilities of ThreadPoolExecutor (which this class extends) are required.
Tasks scheduled for exactly the same execution time are enabled in first-in-first-out (FIFO) order of submission. When a submitted task is cancelled before it is run, execution is suppressed. By default, such a cancelled task is not automatically removed from the work queue until its delay elapses. While this enables further inspection and monitoring, it may also cause unbounded retention of cancelled tasks. To avoid this, set setRemoveOnCancelPolicy to true, which causes tasks to be immediately removed from the work queue at time of cancellation.
It acts as a fixed-sized pool using corePoolSize threads and an unbounded queue, adjustments to maximumPoolSize have no useful effect.

可以看出,定时任务线程池设置固定和核心线程数和无界任务队列,最大线程数设置无意义,默认为Integer.MAX_VALUE。另外,提交一个cancelled的任务不会执行,且默认不移除任务队列,当时间过了才移除,可能会导致cancelled任务堆积,可设置setRemoveOnCancelPolicy为true。

任务使用private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V>代表

套路也是一样,创建一个任务加入执行schedule或scheduleAtFixedRate方法时,会创建一个ScheduledFutureTask加入任务队列,然后STPE的父类TPE中会在一个无限循环中从任务队列DelayedWorkQueue中拉取任务执行,关于该任务队列:

A DelayedWorkQueue is based on a heap-based data structure like those in DelayQueue and PriorityQueue, except that every ScheduledFutureTask also records its index into the heap array.
This eliminates the need to find a task upon cancellation, greatly speeding up removal (down from O(n) to O(log n)), and reducing garbage retention that would otherwise occur by waiting for the element to rise to top before clearing.

Timer不同的是,该阻塞队列阻塞用的是JUC的Condition::await那一套,而不是Object::wait;相同的是如果获取到的任务还没到达时间,同样也是需要挂起一段时间,挂起用的是Conditioni::awaitNanos,参看如下代码:

static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
	 
       public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    RunnableScheduledFuture<?> first = queue[0];
                    // 队列为空则挂起
                    if (first == null)
                        available.await();
                    // 不为空
                    else {
                    	// 获取第一个要执行的任务的执行还剩多少时间
                        long delay = first.getDelay(NANOSECONDS);
                        // 不剩时间 阻塞队列直接返回
                        if (delay <= 0)
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        // Thread designated to wait for the task at the head of the queue. This variant of the Leader-Follower pattern serves to minimize unnecessary timed waiting.				
                        // leader不为空则挂起
                        if (leader != null)
                            available.await();
                        else {
                            Thread thisThread = Thread.currentThread();
                            // leader为空则该线程设为leader
                            leader = thisThread;
                            try {
                            	// **********
                            	// 挂起相应时间
                            	// **********
                                available.awaitNanos(delay);
                            } finally {
                            	// 清空
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
            	// 唤醒follower
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
        }

}

这里有一个 Leader-Follower Pattern,可参考Explain “Leader/Follower” Pattern
执行完了会将下一个周期要执行的新任务加入任务队列,参见ScheduledFutureTask::run和::reExecutePeriodic

    private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {

 		...
        public void run() {
        	// 是否周期执行
            boolean periodic = isPeriodic();
            // 判断线程池是否关闭,关闭就不执行
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            // 不是周期方法,则直接执行
            else if (!periodic)
                ScheduledFutureTask.super.run();
            // 周期方法执行,并更新下一轮时间,加入任务队列
            else if 
            (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                // 重新加入任务队列
                reExecutePeriodic(outerTask);
            }
        }
        ...
 }

可以看到STPETimer策略一样,获取最近时间的定时任务,如果没到时间线程会挂起executionTime - currentTime时间,不过这个步骤在Timer中是由执行线程TimerThread完成,而不是存储任务的TaskQueue,而在STPE中,该步骤放在了阻塞队列DelayedWorkQueue中完成。

Quartz

案例

	// Job
    static class TestJob implements Job {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println("TestJob executing~");
        }
    }

    public static void main(String[] args) {
		// Scheduler
        Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
        defaultScheduler.start();
		// JobDetail
        JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testJob", "default").build();
		
		// Trigger
        TriggerBuilder<CronTrigger> triggerBuilder = TriggerBuilder.newTrigger()
                .withIdentity("trigger", "default")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ?"));
        CronTrigger cronTrigger = triggerBuilder.build();

        defaultScheduler.scheduleJob(jobDetail,cronTrigger);
   }

简介

  • Job: 任务,通常用户实现Job接口,业务相关
  • JobDetail: 任务详情,除了包含业务内容Job之外,还有其他信息,如key datamap description等
  • Trigger: 触发器,任务如何触发,如CronTrigger
  • Scheduler: 调度器门面,大管家,协调JobDetail和Trigger,常见的StdScheduler
  • SchedulerFactory: 调度器工厂
  • JobStore: 提供Job和Trigger存储,常见的RAMJobstore内存存储
  • QuartzScheduler : StdScheduler代理调度器中真正的调度器
  • QuartzSchedulerResources: QuartzScheduler中资源管家,提供线程池、JobStore等资源
  • QuartzSchedulerThread: 调度线程,作用类似Timer中的TimerThread
  • 各种监听器: JobListener TriggerListener SchedulerListener

主要的流程都集中在QuartzSchedulerThread::run中,源码略。
可以看到QuartzSchedulerThreadQuartzSchedulerResources.getJobStore().acquireNextTriggers(...)获取一段时间内可以执行的任务,获取失败第一次会通知监听器,后面会有指数退避,但不会通知监听器。后面先判断是否到了运行时间,如果离运行时间在2ms内则直接运行,否则调用Object::wait方法挂起waitTime - now。如果不用挂起,则初始化运行结果TriggerFiredResult,结果中获取TriggerFiredBundle,将TriggerFiredBundle转化为JobRunShell,最后实际运行QuartzSchedulerResources.getThreadPool().runInThread(shell)

QuartzSchedulerResources.getJobStore().acquireNextTriggers(...)中获取任务,其中也会做任务是否过期的判断,如RAMJobStore中:

 public List<OperableTrigger> acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow) {
	...
	// 判断任务是否过期
	if (applyMisfire(tw)) {
                    if (tw.trigger.getNextFireTime() != null) {
                        timeTriggers.add(tw);
                    }
                    continue;
     }

	protected boolean applyMisfire(TriggerWrapper tw) {

        long misfireTime = System.currentTimeMillis();
        if (getMisfireThreshold() > 0) {
            misfireTime -= getMisfireThreshold();
        }

        Date tnft = tw.trigger.getNextFireTime();
        // tw.trigger.getMisfireInstruction() Trigger接口中有两种
        // MISFIRE_INSTRUCTION_SMART_POLICY 根据updateAfterMisfire()判读但
        // MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY 忽略
        // CronTrigger还有两种:
        // MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
        // MISFIRE_INSTRUCTION_DO_NOTHING
        // SimpleTrigger还有两种:
        // MISFIRE_INSTRUCTION_FIRE_NOW
        // MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
        if (tnft == null || tnft.getTime() > misfireTime 
                || tw.trigger.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { 
            return false; 
        }
		...
    }

}


Quartz没有可执行的任务会使用Object::wait方法挂起,整个加锁和挂起等待用的是synchronized和Object::wait那一套,和Timer类似。不同的是,Quartz每次获取的是一段时间内可执行的一批任务。

分布式调度平台XXL-JOB

在这里插入图片描述

XXL-JOB 分布式任务调度平台

分布式调用平台涉及分布式环境的交互,所以rpc是不可避免地,XXL-JOB中使用netty框架构建了基于HTTP的rpc交互方式(XXL-RPC)

  • xxl-job-admin:调度中心(基于SpringBoot)
  • xxl-job-core:公共依赖(客户端依赖)

其中,调度中心是一个前后端不分离、完整的Spring Web项目,完成任务调度,xxl-job-admin同时依赖xxl-job-core。而每个执行器(任务执行的客户端,即业务集群节点)也需要添加依赖xxl-job-core,在执行器端的该依赖中,XXL基于Netty构建了自己的http接收方式来接收来自调度中心的各种请求,如:任务执行、终止、日志请求,参见EmbedServerEmbedHttpServerHandler;执行器客户端在启动时也要向调度中心注册该执行器(客户端集群节点信息,非任务信息,BEAN模式下任务维护在执行器端的ConcurrentHashMap中,不向调度中心注册),同时调度中心会通过Java原生http向各个执行器发送心跳来检测是否存活以及检测任务执行状态,执行器执行将结果放入一个结果阻塞队列,线程从队列中获取结果向调度中心发送消息,该发送过程也是通过Java原生http完成的,调度中心和执行发送http请求共用XxlJobRemotingUtil工具类来完成。

从以上的描述可以看出:

调度中心执行器
http发送方式java原生httpjava原生http
http接收方式Spring WebNetty

调度中心用于和执行器交互的api(Spring Web构建)只有3个,可视化平台还有其他的不做讨论:

  • /api/callback : 执行器执行回调任务结果
  • /api/registry : 执行器注册和心跳检测
  • /api/registryRemove : 执行器注册摘除时使用,注册摘除后的执行器不参与任务调度与执行

执行器的api(netty构建)有5个:

  • /beat : 调度中心Failover路由模式下主动检测执行器是否在线
  • /idleBeat : 调度中心检测指定执行器上指定任务是否忙碌(运行中)
  • /run : 触发任务执行
  • /kill : 终止任务
  • /log : 终止任务,滚动方式加载

注意,心跳检测是执行器向调度中心每隔30s向*/api/registry*发送心跳(参见ExecutorRegistryThread),该api同样也是注册接口;而执行器端的 /beat是调度中心在Failover路由模式下向执行器分配任务时主动检测时用的,只能说作者api命名地太抽象了🙈。

调度中心调度同样和上面地几个调度方式差不多,XXL-JOB在一个while(!stop)循环中,向数据库查询一段时间内(默认接下来的5000ms)地任务, 然后判断任务是否过期,如果过期超过5000ms要看你的过期策略是什么;如果过期没超过5000ms就立即执行,且看看你下次执行是否又在下一个5000ms内,在的话加入时间轮;时间没到也加入时间轮。时间轮中存放key和对应的List<jobInfo.getId()>,其中key=((jobInfo.getTriggerNextTime()/1000)%60),然后另外有一个线程每隔一秒拿出里面的List<jobInfo.getId()>遍历获取任务分配给执行器执行,分配路由策略有多种:

  • Busyover
  • ConsistentHash
  • Failover
  • First
  • Last
  • LFU
  • LRU
  • Random
  • Round

执行完了要更新任务下一轮触发时间更新数据库,包括任务执行结果、日志、下一轮任务trigger时间等。以上代码主要集中在JobScheduleHelper类中。

可以看到在任务时间没到之前,XXL-JOB不像之前的定时任务那样采用线程挂起的方式,而是使用时间轮存储任务id,另起一个线程每隔一秒从时间轮获取当前时间的任务id来分配给执行器执行。

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

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

相关文章

电脑重装系统后会怎样?

​有小伙伴的电脑系统运行缓慢卡顿&#xff0c;现在想通过重装系统来解决问题。咨询电脑重装系统会怎么样对系统有影响吗&#xff0c;现在小编就带大家看看电脑重装系统后会怎样。 方法/步骤&#xff1a; 一、电脑重装系统会怎么样 1、我们的电脑重装系统后&#xff0c;电脑…

Java面试题总结 | Java基础部分(持续更新)

Java基础 文章目录Java基础一个Java文件里可以有多个类吗&#xff08;不含内部类&#xff09;&#xff1f;创建对象的方法面向对象和面向过程简述自动装箱拆箱Java代码块执行顺序java中的基本数据类型对应的字节数包装类型和基本数据类型的场景java中的关键字分类final关键字st…

【微信小程序】小程序基础入门01

&#x1f609;博主&#xff1a;初映CY的前说(前端领域) ,&#x1f4d2;本文核心&#xff1a;微信小程序的入门介绍 【前言】小程序是一种不需要下载、安装即可使用的应用&#xff0c;它实现了应用触手可及的梦想&#xff0c;用户扫一扫或者搜一下就能打开应用&#xff0c;也实现…

Flink 优化 (三) --------- 反压处理

目录一、概述1. 反压的理解2. 反压的危害二、定位反压节点1. 利用 Flink Web UI 定位2. 利用 Metrics 定位三、反压的原因及处理1. 查看是否数据倾斜2. 使用火焰图分析3. 分析 GC 情况4. 外部组件交互一、概述 Flink 网络流控及反压的介绍&#xff1a;https://flink-learning.…

threejs-后期通道效果汇总

文章目录前言后期处理通道汇总简单通道效果FilmPassDotScreenPassBloomPassUnrealBloomPassOutlinePassGlitchPassHalftonePass高级通道效果掩码效果MaskPass景深效果 BokehPass景自定义效果 ShaderPass总结前言 Threejs提供了很多后期处理通道&#xff0c;配合 THREE.EffectC…

【并发编程Python】一文了解Python并发编程,协程、线程、进程

并发编程简介和一些前缀知识 并发编程是使得程序大幅度提速的。在并发编程中&#xff0c;程序可以同一时间执行多个任务&#xff0c;这有助于提高程序的吞吐量和响应时间。并发编程设计的主要概念包括线程、锁、同步、信号量、进程间通信等。 前缀知识&#xff1a; IO&#x…

信息系统项目管理师第四版知识摘编:第22章 组织通用治理​

第22章 组织通用治理​ 组织治理是协调组织利益相关者之间关系的一种制度安排&#xff0c;目标是为了确保组织的高效决策&#xff0c;实现利益相关者之间的利益均衡&#xff0c;提高组织的绩效&#xff0c;确保组织运行的可持续发展。​ 22.1组织战略​ 组织战略是组织高质量…

一文读懂:低代码开发平台对企业效益有什么作用?

一文读懂&#xff1a;低代码开发平台对企业效益有什么作用&#xff1f; 近年来&#xff0c;企业数字化转型的需求越来越迫切&#xff0c;但面临着IT人才不足、成本高昂等痛点问题&#xff0c;于是零代码平台应运而生&#xff0c;成为企业数字化转型的重要工具。 市面上的零代…

基于支持向量机SVM的脑部肿瘤识别,脑电波样本熵提取

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 SVM应用实例,基于SVM的的脑部肿瘤识别分类预测 代码 结果分析 展望 支持向量机SVM的详细原理 SVM的定义 支持向量机(support vector machines, SVM)是一种二分类模型,它…

Spring boot+Vue博客平台:文章列表展示、文章分类与标签管理模块实现

本文将详细介绍如何实现博客平台中的文章列表展示、文章分类与标签管理功能&#xff0c;包括前端的Vue组件设计和后端的Spring Boot接口实现。在阅读本文后&#xff0c;您将了解如何设计和实现高效、易用的文章列表展示、文章分类与标签管理功能。 一、文章列表展示 1.设计思…

电脑蓝屏错误MACHINE-CHECK-EXCEPTION重装系统教程

电脑蓝屏错误MACHINE-CHECK-EXCEPTION重装系统教程分享。最近有用户电脑遇到了蓝屏问题&#xff0c;正常使用电脑的时候常常会出现了蓝屏错误代码“MACHINE-CHECK-EXCEPTION”。那么遇到这个问题要怎么去进行系统的重装呢&#xff1f;来看看以下的具体操作方法教学吧。 准备工作…

JVM/GC/CMS

CMS (Concurrent Mark Sweep) jdk1.4后期版本开始引入的新gc算法ParNew(新生代) CMS(老年代)组合使用使用标记-清除算法目标&#xff1a;适合于B/S等对响应时间要求高的场景缺点&#xff1a;运行结束产生大量空间碎片缺点&#xff1a;由于分配给用户使用的老年代空间不足造成…

一文快速回顾 Servlet、Filter、Listener

什么是Servlet&#xff1f; 前置知识&#xff1a; Web 服务器&#xff1a;可以指硬件上的&#xff0c;也可以指软件上的。从硬件的角度来说&#xff0c; Web 服务器指的就是一台存储了网络服务软件的计算机&#xff1b;从软件的角度来说&#xff0c; Web 服务器指的是一种软件…

使用codon加速你的python程序

使用codon加速你的python程序 作为高性能 Python 编译器&#xff0c;Codon 可将 Python 代码编译为本机机器代码&#xff0c;而无需任何运行时开销。在单线程上&#xff0c;Python 的典型加速大约为 10-100 倍或更多。Codon 的性能通常与 C/C 的性能相当。与 Python 不同&#…

Three.js教程:第一个3D场景

推荐&#xff1a;将NSDT场景编辑器加入你3D工具链其他工具系列&#xff1a;NSDT简石数字孪生下面的代码完整展示了通过three.js引擎创建的一个三维场景&#xff0c;在场景中绘制并渲染了一个立方体的效果&#xff0c;为了大家更好的宏观了解three.js引擎&#xff0c; 尽量使用了…

【linux】进程和线程的几种状态及状态切换

文章目录一、进程的状态1.1 进程的三种状态1.2 三种状态转换图1.3 三种状态之间的转换1.4 linux下的进程进程状态二、线程的状态三、总结一、进程的状态 1.1 进程的三种状态 进程状态&#xff1a;一个进程的生命周期可以划分为一组状态&#xff0c;这些状态刻画了整个进程。进…

安装spacy+zh_core_web_sm避坑指南

目录 一、spacy简介 二、安装spacy 三、安装zh_core_web_sm 四、安装en_core_web_sm 五、效果测试 5.1 英文测试 5.2 中文测试 一、spacy简介 spacy是Python自然语言处理&#xff08;NLP&#xff09;软件包&#xff0c;可以对自然语言文本做词性分析、命名实体识别、依赖…

Java数组的四种拷贝方式

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

ERTEC200P-2 PROFINET设备完全开发手册(3-2)

周期数据分为两大类&#xff0c;输出数据OutputData和输入数据InputData&#xff0c;输出数据是PLC发送给设备的&#xff1b;输入数据是设备发送给PLC。如果采用标准接口&#xff08;SI&#xff09;&#xff0c;读取输出数据和写入输入数据都是一次初始化数据读写调用和一次/多…

【ChatGPT】多国“围堵”,万人抵制,AI发展的红线到底在哪?

个人主页&#xff1a;【&#x1f60a;个人主页】 文章目录前言Chatgpt&#x1f4bb;&#x1f4bb;&#x1f4bb;多国拟发ChatGPT禁令&#x1f232;&#x1f232;&#x1f232;开端发展高潮联名抵制自我辩解&#x1f39b;️&#x1f39b;️&#x1f39b;️名家争言比尔盖茨&…