Java编程--定时器/线程池/工厂模式/ ThreadPoolExecutor

news2024/9/19 10:43:45

       前言 

        逆水行舟,不进则退!!!     


       目录

       什么是定时器      

       实现一个定时器 

自己实现一个定时器

       什么是线程池 

        线程池的使用:

什么是工厂模式?

自己实现一个线程池:

       ThreadPoolExecutor 类

什么是Runnable 任务?

什么是 Callable 任务?

获取异步的执行结果 是什么意思?

ThreadPoolExecutor类的构造方法有7个参数,


       什么是定时器      

        在Java编程中,定时器是一种工具,它用于在指定的时间点主动触发某个事件,而无需外力去开启或启动。这种机制可以节省人力并实现统一管理。 其中,java.util.Timer类是最常用的定时器实现方式,它允许开发者安排在指定时间运行的任务。 使用Timer类创建定时器主要包括以下步骤:

        首先,创建一个Timer对象;

        其次,创建一个TimerTask对象,该对象包含了要执行的代码;

        然后,将TimerTask对象添加到Timer对象中;

        最后,调用Timer对象的schedule方法来安排任务的执行。

         此外,定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以Timer对象一般又和多线程技术结合紧密。

        定时器的使用:

import java.util.Timer;
import java.util.TimerTask;

public class ThreadDemo10 {
    public static void main(String[] args) {
        System.out.println("程序启动");
        // Timer 类就是 标准库的定时器
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {   //TimerTask是timer.schedule方法的第一个参数,
                                // 其实就是Runnable,TimerTask就是一个实现了Runnable的抽象类。
                                // 也要通过run() 方法来描述一段代码。
            @Override
            public void run() {
               
                System.out.println("运行定时器任务3");
            }
        }, 3000);  // TimerTask 的第二个参数是,指定的时间,
                        //  意思就是,一段时间后,触发第一个参数描述的代码。

        
        // 多写两个,感受一下定时执行的任务
        timer.schedule(new TimerTask() {   
            @Override
            public void run() {
                System.out.println("运行定时器任务2");
            }
        }, 2000);  



        timer.schedule(new TimerTask() {   

            @Override
            public void run() {
                System.out.println("运行定时器任务1");
            }
        }, 1000); 


    }
}


       实现一个定时器 

自己实现一个定时器

        分析:
               1,让被注册的任务,能够在指定时间被执行。
               2,一个定时器是可以注册 N 个任务的,N 个任务会按照最初约定的时间,按顺序执行。
               若要实现 1, 就需要在定时器内部,单独弄一个线程,让这个线程周期性的扫描,判断任务是否是到时间了。如果到时间了,就执行,没到时间,就再等等。并且 N 个任务也需要保存。
 
 所以,定时器中的核心:
         1, 有一个扫描线程,负责查看时间到没到,到了就执行相应任务
         2, 还要有一个数据结构,来保存所有被注册的任务。
         选用什么数据结构呢:  每个任务都是带着“时间”的, 所以我们这里选用优先级队列来存储。 时间段小的,优先级就高, 此时的扫描线程只用扫描队首元素即可,不必遍历整个队列。

     此处的优先级队列是要在 多线程  环境下使用,要考虑线程安全问题。




import java.util.concurrent.PriorityBlockingQueue;

//开始写 定时器
class MyTimer {
    //扫描线程
    private Thread t = null;

    // 有一个阻塞优先级队列, 来保存任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    //扫描线程的具体实现
    public MyTimer() {
        t = new Thread(() -> {
            while(true) {

                try {
                    //取出队首元素,检查看看队首元素任务是否到时间了,
                    //如果时间没到,就把任务再塞回队列中
                    //如果时间到了,就执行任务。
                    MyTask myTask = queue.take();   // 将任务从 阻塞优先级队列中拿出来。
                    synchronized (this) {          // 这里使用 wait   主要是为了防止 忙等。
                        //  这个 synchronized 本来是放到 wait 那里,
                        //  放到这里是为了 保证 取出任务 和 wait  原子化, 防止在中间线程被调度走而且同时来了新任务。
                        long curTime = System.currentTimeMillis();  //获取当前的时间
                        if (curTime < myTask.getTime()) {
                            //说明当前的时间 还没到要执行任务的时间
                            queue.put(myTask);   // 再把任务 放回到阻塞优先级队列中。


                            //在 put 之后, 进行wait 等待
                            // 等待指定时间
                            this.wait(myTask.getTime() - curTime);  // wait 操作,要搭配 锁 来进行的

                        } else {
                            // 已经到了要执行任务的时间了, 可以开始执行任务了。
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
        t.start();
    }

    //提供一个 schedule() 方法,来注册任务 : 两个参数:
    //第一个参数:执行的 任务
    //第二个参数:执行任务前等待的时间。
    public void schedule(Runnable runnable, long after) {
        //第二个参数这里,需要换算为 : 当前的时刻 + 需要等待的时间。
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (this) {
            this.notify(); // 唤醒一下扫描线程
        }
    }


}


//任务的具体描述
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();
    }

    @Override
    public int compareTo(MyTask o) {
        // 返回   小于0, 大于0, 0  这三个数字
        // this 比 o 大, 返回  >0;
        return (int)(this.time - o.time);
    }
}





public class ThreadDemo11 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        }, 1000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        }, 2000);
    }
}

        注意:

                1,在创建一个新任务那里,在将任务创建好,放入堆中之后,有一步唤醒操作,这里的唤醒操作是唤醒扫描线程那里的等待。在扫描线程中,会将最优先的任务拿出来看看,如果还没到执行的时间,那就再将任务放回到优先级队列中,并且阻塞需要等待的时间。就是这里,如果在阻塞等待时间内,又来了一个优先级更高的任务(时间更短,比刚刚阻塞等待的时间还短),就需要扫描线程重新去优先级队列中拿出最优先的任务,重新计算阻塞等待的时间,然后阻塞等待。所以说,这里的这个唤醒机制很有必要。 

                2,在扫描线程中,synchronized(this) 这行代码 锁住的是实现MyTimer类 的 对象,同一时间,只能有一个线程可以访问到这个对象。

                拓展:如果类中有多个静态方法,使用synchronized 修饰其中一个静态方法,那也同样是对整个类进行了加锁,同一时间,只能有一个线程可以访问到该类的任何静态方法。但是并没有对实现这个类的对象加锁。


       什么是线程池 

        线程池: 为了使多线程开发更高效,使多线程的使用更轻便,而产生。事先把需要使用的线程创建好,放到“池”中,后面需要使用的时候,直接从池里获取,用完后也还给 “池”,  这两个动作要比 创建/销毁 更高效。

        创建线程/销毁线程 是交给 操作系统内核 完成的。而从池子里获取/还给池, 是咱们自己用户代码就能实现的,不必交给内核操作。

        什么是操作系统内核?

               答:操作系统内核是操作系统最基本的部分,是一个提供硬件抽象层、磁盘及文件系统控制、多任务等功能的系统软件。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的,所以内核通常提供一种硬件抽象的方法来完成这些操作。

       无论是商业的还是个人开发的操作系统内核,都被视为计算机系统的基石和黑盒。这意味着用户通常不需要知道内核内部是如何实现的,只需要使用该内核提供的服务即可。

       我们不清楚内核的具体行为,也就意味着不可控,当我们将任务交给操作系统内核,何时等到系统的回应也是不可控的,因为系统内核不仅仅这一个任务。所以,相比于内核来说,用户态,执行程序的行为是可控的

        线程池的使用:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 使用标准库中的线程池
public class ThreadDemo12 {
    public static void main(String[] args) {
        // 创建了一个固定线程数目的线程池
        // 这里的创建 使用了工厂模式
        ExecutorService pool = Executors.newFixedThreadPool(10);
        
        for(int i = 0; i <1000; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {       // 这个 run() 方法 不是由主线程调用的,
                    // 而是由线程池中的线程调用。
                    System.out.println("hello " + n);
                }
            });
        }
    }
}

什么是工厂模式?

        答:用一句话表示:使用普通方法,来代替构造方法创建对象。那为什么需要代替构造方法了?因为在某些情况下,我们可能要构造多个不同情况的对象,但是使用构造方法的重载又有一些局限性(重载方法 名称相同,参数个数和类型不同),这种情况对我们实现一些代码时有些限制,于是就有了工厂模式。

        普通方法,方法名字没有限制的,因此有多种方法构造,就可以直接使用不同的方法名即可,此时,方法的参数是否要区分就已经不重要了。

自己实现一个线程池:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool {

    // 此处不涉及到时间, 此处只有任务,就直接使用 Runnable 即可
    // 阻塞队列中元素的类型为 Runnable 接口类型
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();


    // n 表示线程的数量
    public  MyThreadPool(int n) {
        // 在这里创建线程
        // for循环来创建线程
        for(int i = 0; i < n; i++) {
           
            Thread t = new Thread(() -> {
                
               // while 循环来让线程不断地从队列中取任务。
                while(true) {
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    // 注册任务给线程池
    public void submit (Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}


public class ThreadDemo16 {
    public static void main(String[] args) {
        MyThreadPool pool = new MyThreadPool(10);
        for(int i = 0; i < 1000; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + n);
                }
            });
        }
    }
}


       ThreadPoolExecutor 类

        

 ThreadPoolExecutor : 最原生的线程池

        ThreadPoolExecutor是Java中线程池的核心实现类,它主要用来执行被提交的任务。通过ThreadPoolExecutor的execute()方法,用户可以提交Runnable任务进行执行;而通过submit()方法,用户不仅可以提交Runnable任务和Callable任务,还能获取异步的执行结果。

       使用ThreadPoolExecutor的主要优点在于,当系统中频繁地创建线程时,如果线程过多,会带来调度开销,进而影响缓存局部性和整体性能。而通过使用线程池,可以避免在处理短时间任务时频繁地创建与销毁线程所带来的代价。线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,这既保证了内核的充分利用,又防止了过分调度。

什么是Runnable 任务?

        在Java中,Runnable接口表示一个可以被线程执行的任务,它本身是一个抽象任务,只定义了要执行的操作,并没有具体的实现。这个接口里包含一个无返回值的方法run()。Runnable没有启动线程的能力,因此必须使用Thread类中的start方法才能够启动一个线程。Runnable的run()方法定义没有抛出任何异常,所以任何的Checked Exception都需要在run()实现方法中自行处理。

什么是 Callable 任务?

        与Runnable相似的Callable接口也能被线程执行,但Callable接口的task能返回一个结果,也可以抛出Exception。两者都可以被ExecutorService执行,其中Callable的call()方法只能通过ExecutorService的submit(Callable task)方法来执行,并且会返回一个Future,是表示任务等待完成的对象。

获取异步的执行结果 是什么意思?

        异步执行结果是指在程序执行过程中,某个耗时较长的操作(通常是IO操作或者计算密集型任务)在执行时并不会阻止其它操作的进行,当这个耗时操作完成时,该操作的结果将会被后续的操作或者函数使用。异步编程是一种提高程序性能的方式,它允许同一时间发生(处理)多个事件。

        例如,在Java中,当我们调用一个耗时较长的功能(方法)时,如网络请求或大规模计算,这个方法并不会阻塞程序的执行流程,程序会继续往下执行。当这个功能执行完毕时,比如数据接收完毕或者计算完成,程序能够获得执行完毕的消息或能够访问到执行的结果(如果有返回值或需要返回值时)。

        另外,在更现代的编程模式中,如回调函数、Promises、Futures和CompletableFuture等,可以以非阻塞的方式获取任务执行结果,这种方式不仅提高了程序的响应速度和执行效率,而且使得代码逻辑更加清晰易懂。

ThreadPoolExecutor类的构造方法有7个参数,

分别是:

        1. corePoolSize:线程池中会维护一个最小的线程数量,这些线程处理空闲状态,他们也不会 被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

        2. maximumPoolSize:线程池允许的最大线程数量。

        3. keepAliveTime:当线程池中的线程数量超过corePoolSize时,多余的空闲线程的存活时间。

        4. unit:keepAliveTime的时间单位。

        5. workQueue:任务队列,用于存放待执行的任务。

        6. threadFactory:创建新线程的工具类。

        7. handler:当线程池中的线程数量超过maximumPoolSize且任务队列已满时,如何处理新提交的任务。

注解:

        1,corePoolSize 核心线程数 和 maximumPoolSize 最大线程数 的区别:

       核心线程数是指线程池中一直保持的线程数,哪怕它们处于空闲状态。这意味着即使线程池中没有任务,这些核心线程也会一直保持存在,以便于快速响应新的任务请求

       最大线程数则是指线程池中允许的最大线程数,这包括了空闲线程和正在工作的线程。如果线程池的任务队列已满且所有的核心线程都在工作,那么此时如果有新任务提交,线程池会根据设置的策略决定是否创建新的线程

       举两个例子:

                1),在一家公司中, 核心线程数就是正式员工, 最大线程数就是 正式员工 + 实习生;当所有的正式员工都在忙碌,并且还有新的任务下来,这时就会招收一些实习生来缓解压力。如果长时间任务比较少,实习生一直在摸鱼(线程空闲),那么就会销毁这个线程,但是呢核心线程数并不会被销毁(即使线程空闲)

                2),假设你开了一家餐厅,这家餐厅的服务员就是线程,而顾客就是任务。核心线程数就好比是你固定的服务员人数,无论餐厅是否忙碌,这些服务员都会在岗位上待命,随时准备为顾客服务。           最大线程数则好比是餐厅能够容纳的最大服务员数量,包括正在工作的和待命的。如果所有的服务员都在工作,并且还有新的顾客进来,那么就需要根据餐厅的规定来决定是否需要再雇佣新的服务员。                   例如,你的餐厅规定,当所有服务员都在工作时,如果有新的顾客进来,那么就不能再接待更多的顾客了,除非有服务员完成他们的工作并腾出位置。这就是最大线程数的作用。

        2,keepAliveTime  简单解释就是 实习生可以摸鱼的最大时间,超过这个线程就销毁。unit: keepAliveTime 是摸鱼时间的时间单位

        3,handler  其实就是一个拒绝策略  ThreadPoolExecutor类的构造方法中,处理提交任务超过线程池最大容量的拒绝策略有四种:

                1) AbortPolicy(默认):直接抛出RejectedExecutionException异常,阻止系统正常运行。

                2) DiscardOldestPolicy:丢弃等待队列中最旧的任务,然后重新尝试执行任务(重复此过程直到能够执行任务为止)。

                3) DiscardPolicy:直接丢弃新来的任务,不抛出异常。

                4) CallerRunsPolicy:让调用者自己运行任务。


        我是专注学习的章鱼哥~

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

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

相关文章

卓越进行时 | 信息安全测试公益培训班报名!先到先得

为配合推进江苏省网络安全工作的部署实施&#xff0c;培训网络安全技术人才&#xff0c;针对全省网络技术骨干人员&#xff0c;近期&#xff0c;由江苏省网络空间安全学会主办、南京赛宁信息技术有限公司承办的“网络安全技能课堂-信息安全测试培训班”在网络安全卓越中心正式举…

完全免费!超好用的IDEA插件推荐:Apipost-Helper

Idea 是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序,Idea 还具有许多插件和扩展&#xff0c;可以根据开发人员的需要进行定制和扩展&#xff0c;从而提高开发效率,今天我们就来介绍一款国产的…

ceph的体系结构

文章目录 CephCeph的体系结构对象存储RADOSOSDOSD的状态osd状态检测 数据寻址file--->Object映射Object--->pg映射pg--->osd思考&#xff1a;为什么要在Object和osd之间增加一层pg的映射呢&#xff1f; 存储池monitormonitor与客户端的通信Monitor与osd的通信 数据操作…

7-爬虫-中间件和下载中间件(加代理,加请求头,加cookie)、scrapy集成selenium、源码去重规则(布隆过滤器)、分布式爬虫

0 持久化(pipelines.py)使用步骤 1 爬虫中间件和下载中间件 1.1 爬虫中间件(一般不用) 1.2 下载中间件&#xff08;代理&#xff0c;加请求头&#xff0c;加cookie&#xff09; 1.2.1 加请求头(加到请求对象中) 1.2.2 加cookie 1.2.3 加代理 2 scrapy集成selenium 3 源码去重…

【Transformer从零开始代码实现 pytoch版】(五)总架构类的实现

Transformer总架构 在实现完输入部分、编码器、解码器和输出部分之后&#xff0c;就可以封装各个部件为一个完整的实体类了。 【Transformer从零开始代码实现 pytoch版】&#xff08;一&#xff09;输入部件&#xff1a;embeddingpositionalEncoding 【Transformer从零开始代…

Power Automate-变量和excel表数据的应用

前提表格 Power Automate连接excel请参考&#xff1a;SharePoint-连接Excel-CSDN博客 需求1&#xff1a;计算表格中某列的和 添加操作&#xff0c;搜索变量&#xff0c;选择初始化变量 添加变量的名称、类型和初始值 再新增操作&#xff0c;搜索Excel&#xff0c;点击查看更多…

Kubernetes介绍和环境部署

文章目录 Kubernetes一、Kubernetes介绍1.Kubernetes简介2.Kubernetes概念3.Kubernetes功能4.Kubernetes工作原理5.kubernetes组件6.Kubernetes优缺点 二、Kubernetes环境部署环境基本配置1.所有节点安装docker2.所有节点安装kubeadm、kubelet、kubectl添加yum源containerd配置…

查询数据表格中的数据

1.创建这个表至少20个 1&#xff09;创建数据库&#xff1a;create database 四川信息职业技术; 2&#xff09;创建数据表 3&#xff09;插入数据&#xff08;第一条代码修改了一下手机号码的字段类型&#xff09; 2.统计表中的人数 如果你想根据某个特定的列来统计人数&…

Jenkins在Linux环境下的安装与配置

Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成&#xff08;CI&#xff09;工具&#xff0c;用于解决持续重复的部署、监控工作&#xff1b;它一个开放易用的软件平台&#xff0c;大大简化软件的持续集成。 安装Jenkins 1.使用docker安装 2.本地下载je…

Python数据结构:元组(Tuple)详解

1.介绍和基础操作 Python中的元组&#xff08;Tuple&#xff09;是不可变有序序列&#xff0c;可以容纳任意数据类型&#xff08;包括数字、字符串、布尔型、列表、字典等&#xff09;的元素&#xff0c;通常用圆括号() 包裹。与列表&#xff08;List&#xff09;类似&#xff…

[IJKPLAYER]基于DEMO分析IJKPLAYER(整理版本)

背景 博主主要是从事C语言开发&#xff0c;因此本文着重强调FFMPEG部分&#xff0c;关于JAVA应用和框架层只是一笔带过。IJKPLAYER的实质是对FFMPEG项目中的ffplayer程序进行的二次封装&#xff0c;通过JNI方式完成对外提供JAVA接口。 1.目录结构 activities:包含了demo的所有…

react函数式组件props形式父向子传参

父组件中定义 子组件中触发回调传值 import { useState } from "react"; function Son(params) {const [count, setCount] useState(0);function handleClick() {console.log(params, paramsparamsparamsparamsparamsparams);params.onClick(111)setCount(count 1…

多个微信快速同步发圈

做营销最重要的任务是什么&#xff1f; 毋庸置疑&#xff0c;就是发布朋友圈。 为什么要发圈呢&#xff1f; 现在社交媒体中&#xff0c;微信不管在生活上、工作上都是不可或缺的工具&#xff0c;而朋友圈是微信中社交场景之一&#xff0c;也是很多企业作为推广产品和服务的重…

腾讯云服务器多少钱一年?2023年腾讯云优惠云服务器推荐

作为一名程序员&#xff0c;技术的突飞猛进是从拥有第一台云服务器开始的。那时&#xff0c;我开始尝试使用Linux系统&#xff0c;并成功上线了自己的第一个小程序。自此之后&#xff0c;我和我的同事们都开始拥有自己的云服务器&#xff0c;用来搭建各种小项目或者好玩的东西。…

OpenAtom OpenHarmony三方库创建发布及安全隐私检测

OpenAtom OpenHarmony三方库&#xff08;以下简称“三方库”或“包”&#xff09;&#xff0c;是经过验证可在OpenHarmony系统上可重复使用的软件组件&#xff0c;可帮助开发者快速开发OpenHarmony应用。三方库根据其开发语言分为2种&#xff0c;一种是使用JavaScript和TypeScr…

wpf devexpress设置行和编辑器

如下教程示范如何计算行布局&#xff0c;特定的表格单元编辑器&#xff0c;和格式化显示值。这个教程基于前一个文章 选择行显示 GridControl为所有字段生成行和绑定数据源&#xff0c;如果AutoGenerateColumns 属性选择AddNew。添加行到GridControl精确显示为特别的几行设置。…

Containerd接入Harbor仓库

在使用容器时&#xff0c;避免不了会使用到私有仓库&#xff0c;一般都是采用 harbor 作为私有仓库&#xff0c;docker 对接 harbor 仓库非常简单&#xff0c;哪 containerd 如何对接 harbor 呢&#xff1f; 在内网使用 harbor 根据个人习惯&#xff0c;一般都是非 http 并且是…

【SpringBoot3+Vue3】一【基础篇】

目录 一、Spring Boot概述 1、Spring Boot 特性 1.1 起步依赖 1.2 自动配置 1.3 其他特性 1.3.1 内嵌的Tomcat、Jetty (无需部署WAR文件) 1.3.2 外部化配置 1.3.3 不需要XML配置(properties/yml) 二、Spring Boot入门 1、一个入门程序需求 2、步骤 2.1 创建Maven工…

智能配方颗粒管理系统解决方案,专业实现中医药产业数字化-亿发

“中药配方颗粒”&#xff0c;又被称为免煎中药&#xff0c;源自传统中药饮片&#xff0c;经过提取、分离、浓缩、干燥、制粒、包装等工艺加工而成。这种新型配方药物完整保留了原中药饮片的所有特性。既能满足医师的辨证论治和随症加减需求&#xff0c;同时具备强劲好人高效的…

Python实现猎人猎物优化算法(HPO)优化XGBoost回归模型(XGBRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…