聊聊定时任务

本篇文章会持续更新.

摘要:

        本篇主要是记录学习、工作中遇到有关定时任务的经历。从学习了解到的创建线程的方法,到工作项目中创建线程池,后面使用调度框架这样的一个过程。本篇文章包含正文、案例、面试,正文是个人的一些理解,案例当前是CV了,面试是突击求职。

正文:       

        在学习、工作项目中,常常会有很多的业务逻辑,这些业务逻辑通常分为2类,一类为直接执行返回的,另外一类就是定时任务异步执行的。

        今天2024年06月21 08:37:27 星期五,来聊聊什么最基础的内容,什么是线程,什么是进程。大家都知道我们的pc、phone(后面都以pc举例)吧,pc上运行每个程序可以理解为一个进程,此时,可能会想,pc上我有那么多的程序,但是pc的cpu的线程也就那么多(目前锐龙线程撕裂者7980X有高达64核心128线程),这么多程序是怎么运行的。cpu是计算单元,操作系统中包含调度和调用硬件的能力,程序是首先运行在操作系统中的,其次程序是被操作系统调度执行的。操作系统让每个程序占用cpu(使用cpu处理计算的时间片)按照顺序、优化级等不同策略调度的。

        回到线程,线程是程序内同步或异步执行的子程序。1个进程内包含n个线程,当然,n个线程是根据需求、架构设计、开发实现的。那么怎么创建进程呢,你的springboot启动后就是一个进程,它占用一个端口,也占有pc内操作系统内执行程序的一个PID,可以根据端口进行访问服务,可以根据PID将进程kill掉。那么怎么创建线程内,在Java中,有extends Thread继承线程类、implements Runnable实现Runnable、implements Callable<T>实现Callable、使用线程池、lambda匿名内部类、虚拟线程等这么多方式。

        工作中使用最多的当然是每种都有了。虚拟线程没怎么用,最近项目框架刚升级到JDK21,后续虚拟线程就用的多了。虚拟线程了解了一些,首先是创造方式有thread建造者静态方法,另外就是虚拟线程的新特性方法了try(resources){}。项目中使用继承Thread和Runnable方式主要是在需要单独控制,另外也需要动态传参这种需求会用。使用线程池的主要是配合Thread和Runnable方式创建,这种场景一般是异步任务,定时任务居多。实现Callable得到返回值这种,说实话目前我用的不多,如果要用,应该会用CompletableFuture组合任务这种方式,实际这种需求很少,一般在多系统、多模块间调用使用,另外,多模块调用目前大多以dubbo、feign方式就像本地调用方法使用了。lambda匿名内部类当然最多了,测试中使用最简单,另外在项目中如果遇到异步任务,不需要关注线程安全(数据)情况下只用这种,创建简单,但是注释最好写的好点,避免后面看到异步任务不知道干啥的。   

        今天2024年06月23 08:34:42 星期日,补充了一下场景,创建线程的基础方式,包含extends Thread继承线程类、implements Runnable实现Runnable、implements Callable<T>实现Callable、匿名内部类四种方式,另外分别在底部演示了如何调用的基础方法。这些语法有点大基础了,不一定会对大家有用。实际工作中,首先要掌握是否需要线程池,其次是线程池如何去创建(参数如何设置),另外还有如何保证线程安全和异常了怎么办。

        在不同业务逻辑中,这里以接口为例吧,当一个接口的吞吐量和返回响应时间不满足需求时,需要对其进行优化,不需要直接处理的任务可以考虑使用异步队列异步线程任务处理。吞吐量是什么?在Java编程中,吞吐量通常表示在特定时间内完成的任务或操作数量。响应时间通常表示接口从客户端发起调用到客户端收到响应的时间。

        提高吞吐量和响应时间往往是相辅相成的,增大吞吐量可以适量提高执行线程池的参数设置,响应时间通常跟业务逻辑、实现方式、架构选择有很大关系。一个功能,一个业务,一个按钮对应后端可能就是一个接口,有很多程序员选择通用化,让前端调整不同的参数,虽然能实现,但是在特殊处理上,优化上其实可以做的更好,响应有更快的实现方式。在业务、事务类功能上,不可避免会使用到数据库事务、锁等机制;在查询类功能上,可以考虑缓存、搜索引擎es等技术提高性能。这些在本文不会深入探讨,还是以定时任务为主。

        今天主要来探讨一下线程池的创建,和参数如何设定的话题。在当前Java微服务体系中,web容器tomcat、 Undertow实现中,就使用了线程池,在触发接口时,将接口提交到线程池中进行执行,将响应结果进行返回。你可能见过,看图:(ServerProperties配置属性类中)

        可以通过配置文件修改线程池的线程池,承载接受客户端发起的请求数和执行线程数。

        tomcat

  • server.tomcat.threads.max: 最大工作线程数,默认值为200。
  • server.tomcat.threads.min-spare: 最小备用线程数,默认值为10。

        Jetty

  • server.jetty.threads.max: 最大线程数。
  • server.jetty.threads.min: 最小线程数。
  • server.jetty.threads.idle-timeout: 线程空闲超时时间(以毫秒为单位)。

        Undertow

  • server.undertow.threads.io: IO线程数。
  • server.undertow.threads.worker: 工作线程数。

        这些参数实际是线程池中的参数。以Tomcat容器为例,见源码:

        另外Undertow、jetty都类似,需要创建不同的执行器池,池子设置好大小。线程池的创建方式有使用Executors工具类创造、使用ThreadPoolExecutor构造、创造虚拟线程池等。具体参数和创造方法见案例“线程池的创建方式章节”。

        作为后端开发,可见线程池的使用无处不在,接口的运行线程就是在线程池内。接口的调优同样包含线程池的调优,为何现在用Undertow容器,后续单独介绍一下Undertow容器和tomcat的区别。那么回到文章的题目,聊聊定时任务,定时任务和线程池有什么关系?任务是执行逻辑,在程序中是作为线程运行的任务,线程要管理吧,所以线程池很重要,用于管理和运行线程。本文到这里关于定时概念提的还是很少的。定时任务,是根据需求、场景在特定时间调度线程运行的。常见开发中调度技术有Spring的schedule注解、quartz框架、ElasticJob、XXL-job、JUC中的ScheduledExecutorService、Timer类等。

        我参与项目中使用Spring Task Scheduling和XXL-job最多,但SpringTaskScheduling也有小坑。知晓里面的注解、参数很重要。另外@Async是什么,和@Async有什么关系?后续见案例中进行说明。定时任务是在程序中指定的,在任务多了后及分布式环境,就要使用XXL-job这种有界面的框架了。XXL-job集成很简单,嵌入代码很弱,几乎么有,另外提供admin任务管理页面,可以对所有任务进行管理、调度、查看日志。日志呀!这个太好了,在排查问题时不要太方便了。

        2024年06月23 20:07:41 星期日,今天整理完毕,包含了创建线程池、参数和案例分享。实际工作拷贝SpringBoot的线程池案例即可。

        2024年06月25 14:40:52 星期二,今天梳理一下SpringBoot中的@Async的使用方法和注意事项。注意点:

        1、启用@Async异步任务的方法:@EnableAsync

        2、类上面慎用@Async注解,在类上面使用说明整个类所有方法为异步任务,不能有普通返回值的方法,否则会报错。eg:Invalid return type for async method (only Future and void supported): class java.lang.String。

        3、使用@Async注解的方法,如果需要返回值,返回值就要使用 CompletableFuture类。当普通调用时,实际是将@Async注解的方法提交到了线程池执行,默认情况下调用主体函数中会阻塞等待异步任务的返回值。

        4、当@Async注解的方法,主体函数中可以使用CompletableFuture线程编排的其他方法,多个任务,超时情况等。

        使用方法见案例@Async。

        说实话,在项目中使用线程编排的情况真的不多。如果问到,咱们也要知道。首先要了解CompletableFuture返回值都有什么方法,例如可以设置她的前后线程吧,可以设置执行等待超时时间吧,可以获取到结果吧。线程编排使用的工具类为CompletableFuture。可以先猜一下都有什么方法,例如:多个线程只有有一个执行完成后继续执行。具体方法和功能在“CompletableFuture”案例方法中解释。

        2024年06月27 16:36:42 星期四,这俩天有点忙,昨天临时要从杭州去绍兴出差,啊,还遇到暴雨,天~,开着公司的A6。回来后又加了会班,因为redis配置的一个小问题,我擦。定时任务正文也要接近尾声,聊聊定时任务调度管理框架跟面试章节差不多了。目前市面主流的XXL-JOB,我最推荐这个,其他的就可以不用看了,能够满足中小公司的异步定时任务调度的功能,集成简单,对springboot十分友好。

        关于场景,在架构设计中占比非常重要。我目前使用到的场景,定时调度将数据处理后入到es中,另外需要将一些数据进行关联后才可以另外存储。小公司嘛,没有大数据库,只能将数据分批进行处理。中大公司有自己的数仓,使用etl将数据从业务库和数仓进行交换,在数仓基础上,使用对于技术的计算引擎将数据合并、处理,最后保存或更新到对应的库中。业务部门能拿到对应的数据即可展示。

        此时,聊到了大数据中的etl,公司自研、承接、参与的项目情况都不一致,使用适合当前场景的技术架构十分重要。关于定时任务,总结分为3类,第一类:最最基础调度定时任务的场景,使用基础的 Quartz、Spring Schedule即可实现。第二类:当定时调度多了起来,需要管理,需要看日志,需要手动调度,动态设置调度频率场景时,就使用XXL-JOB就准没毛病。第三类:公司建有数仓,拥有大数据平台,自包含调度模块,开发对应的计算插件进行调度,处理后将数据互相流转。

案例

场景

本篇案例以订单数据为背景,订单数据需要按照订单号分片、用户ID分片、商户号分片入到mysql,另外将数据再冗余到es一份。当用户操作后,将订单发向消费队列,开启异步线程进行批量入库处理。

创建线程的基础方式

基础方式包含extends Thread继承线程类、implements Runnable实现Runnable、implements Callable<T>实现Callable、匿名内部类。

extends Thread 

package com.fagejiang.appthread.thread;

import lombok.extern.slf4j.Slf4j;

/**
 * @program: fa-cloud
 * @description: 订单线程
 * @author: <发哥讲Java-694204477@qq.com>
 * @create: 2024-06-23 08:06
 **/
@Slf4j
public class OrderThread extends Thread {

    // 定义 service、dao 等处理器。
    // eg:shardOrderService

    @Override
    public void run() {
        log.info("Start run Thread..");
        // 执行业务逻辑。
        // shardOrderService.do()
    }

}


// 调用:
// 在OrderThread构造器内传入 处理器
// new OrderThread().start();

implements Runnable

@Slf4j
public class OrderRunnable implements Runnable {

    // 定义 service、dao 等处理器。
    // eg:shardOrderService

    @Override
    public void run() {
        log.info("Start run Runnable Thread..");
        // 执行业务逻辑。
        // shardOrderService.do()
    }

}


// 调用
// 在OrderRunnable构造器内传入 处理器
// new Thread(new OrderRunnable()).start();

实现Callable

package com.fagejiang.appthread.thread;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;

/**
 * @program: fa-cloud
 * @description: 订单线程
 * @author: <发哥讲Java-694204477@qq.com>
 * @create: 2024-06-23 08:06
 **/
@Slf4j
public class OrderCallable implements Callable<Boolean> {

    // 定义 service、dao 等处理器。
    // eg:shardOrderService

    @Override
    public Boolean call() throws Exception {
        log.info("Start run Runnable Thread..");
        // 执行业务逻辑。
        // shardOrderService.do()

        // 执行成功
        return true;
    }
}


//调用的2种方法:
 public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<Boolean> booleanFutureTask = new FutureTask<>(new OrderCallable());
        new Thread(booleanFutureTask).start();
        Boolean resultFlag = booleanFutureTask.get();
        System.out.println("resultFlag = " + resultFlag);

        try (
                ExecutorService executorService = Executors.newSingleThreadExecutor();
        ) {
            Future<Boolean> orderSubmitFuture = executorService.submit(new OrderCallable());
            Boolean resultFlag2 = orderSubmitFuture.get();
            System.out.println("resultFlag2 = " + resultFlag2);
        }

        // 防止main执行结束,线程未执行完成。
        ThreadUtil.sleep(Duration.ofSeconds(2));
    }

 匿名内部类,最爱没有之一

package com.fagejiang.appthread.thread;

import com.fagejiang.appthread.utils.ThreadUtil;

import java.time.Duration;
import java.util.concurrent.*;

/**
 * @program: fa-cloud
 * @description:
 * @author: <发哥讲Java-694204477@qq.com>
 * @create: 2024-06-23 08:12
 **/
public class OrderMain {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
      
        // 匿名内部类
        new Thread(() -> {
            // 执行逻辑....
        }).start();
        try (
                ExecutorService executorService = Executors.newSingleThreadExecutor();
        ) {

            Future<Boolean> orderSubmitFuture = executorService.submit(() -> {
                // 执行逻辑....
                return true;
            });
            Boolean resultFlag3 = orderSubmitFuture.get();
            System.out.println("resultFlag3 = " + resultFlag3);
        }
        try (
                ExecutorService executorService = Executors.newSingleThreadExecutor();
        ) {
            executorService.execute(() -> {
                // 执行逻辑....
            });
        }
    }
}

线程池的创建方式

        创建方式有:使用Executors工具类创造、使用ThreadPoolExecutor构造、创造虚拟线程池等。

ThreadPoolExecutor方式

        线程池的常用配置类为:ThreadPoolExecutor

 ThreadPoolExecutor提供的构造方法最多是有7个参数。包含:

1. corePoolSize,创建后不会直接创建corePoolSize大小的核心线程,当添加任务后才会创造。(懒加载)

int corePoolSize核心线程池大小,即线程池中保持活跃的线程数,除非将 allowCoreThreadTimeOut 设置为 true。即使线程处于空闲状态,核心线程也会一直存活。

2. maximumPoolSize

int maximumPoolSize线程池中允许的最大线程数。当任务队列已满时,如果当前线程数小于 maximumPoolSize,则会创建新的线程来处理任务。

3. keepAliveTime,释放资源等待新任务的时间,配合unit一起。

long keepAliveTime:当线程池中的线程数量超过 corePoolSize 时,多余的空闲线程在终止前等待新任务的最长时间。

4. unit

TimeUnit unitkeepAliveTime 参数的时间单位。常见的单位包括 TimeUnit.SECONDSTimeUnit.MILLISECONDS 等。

5. workQueue

BlockingQueue<Runnable> workQueue:用来存储等待执行任务的队列。常用的队列类型有:

  • SynchronousQueue<Runnable>:不存储任务,每个插入操作必须等待相应的删除操作,否则无法继续。
  • LinkedBlockingQueue<Runnable>:基于链表的有界阻塞队列(默认Integer.MAX_VALUE)。
  • ArrayBlockingQueue<Runnable>:基于数组的有界阻塞队列。
  • PriorityBlockingQueue<Runnable>:无界阻塞队列,按照优先级顺序执行任务。

6. threadFactory

ThreadFactory threadFactory:用于创建新线程的工厂。可以使用默认的,也可以自定义以设置线程的名称、优先级等。

7. handler

RejectedExecutionHandler handler:拒绝策略,当任务无法被线程池接受时的处理方式。常见的拒绝策略有:

  • AbortPolicy(默认):直接抛出 RejectedExecutionException 异常。
  • CallerRunsPolicy:调用执行自己的线程运行任务。
  • DiscardPolicy:直接丢弃任务,不予处理。
  • DiscardOldestPolicy:丢弃最旧的未处理任务,然后尝试重新提交任务。
常规方式
package com.fagejiang.appthread;

import com.fagejiang.appthread.utils.ThreadUtil;

import java.time.Duration;
import java.util.concurrent.*;

public class ThreadPool {

    public static final ThreadPoolExecutor threadPool;

    static {
        threadPool = new ThreadPoolExecutor(
                5,
                20,
                30,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(50),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy());
    }

    public static void main(String[] args) throws InterruptedException {
        threadPool.execute(() -> {
            int i = 10 / 1;
            System.out.println(Thread.currentThread().getName() + "running...." + i);
        });
        System.out.println("提交任务完成...");
        ThreadUtil.sleep(Duration.ofSeconds(2));
        // 正常情况下,线程池开启后不会轻易shutdown。
        threadPool.shutdown();
    }

}
 SpringBoot中线程池
package com.fagejiang.cloud.common.web.config;

import cn.hutool.core.thread.ThreadFactoryBuilder;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Optional;
import java.util.concurrent.*;

/**
 * @program: fa-cloud
 * @description: 线程池配置类
 * @author: <航迹者-694204477@qq.com>
 * @create: 2024-06-23 19:07
 **/
@Configuration
@ConditionalOnBean(value = ThreadPoolExecutor.class)
@ConfigurationProperties(prefix = "com.hangjizhe.thread.pool")
@Data
public class ThreadPoolExecutorConfiguration {
    /**
     * 获取当前机器的核数, 不一定准确 请根据实际场景 CPU密集 || IO 密集
     */
    public static final int cpuNum = Runtime.getRuntime().availableProcessors();

    /**
     * 核心线程数
     */
    private Optional<Integer> corePoolSize;
    /**
     * 最大线程数
     */
    private Optional<Integer> maxPoolSize;
    /**
     * 队列长度
     */
    private Optional<Integer> queueCapacity;

    /**
     * 销毁等待时间
     * 销毁等待单位为秒。
     */
    private Optional<Integer> awaitTerminationSeconds;

    @Bean
    public ThreadPoolExecutor getThreadPoolExecutor() {
        // ThreadFactoryBuilder 采用hutool内的工具,自己搜索maven即可。
        // 也可以自己实现ThreadFactory接口,另外也可以使用com.google.guava包等。
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNamePrefix("HangJiZhe-Thread-")
                .build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // 核心线程大小 默认区 CPU 数量
                corePoolSize.orElse(cpuNum),
                // 最大线程大小 默认区 CPU * 2 数量
                maxPoolSize.orElse(cpuNum * 2),
                awaitTerminationSeconds.orElse(60),
                TimeUnit.SECONDS,
                // 队列最大容量,采用LinkedBlockingQueue。
                new LinkedBlockingQueue<>(queueCapacity.orElse(500)),
                // 线程工厂
                threadFactory,
                // 拒绝策略
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 保证核心线程一直存活
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        return threadPoolExecutor;
    }

}

使用SpringBoot中的线程池:(推荐使用)

package com.fagejiang.cloud.common.web.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author <航迹者-694204477@qq.com>
 * @description 修改SpringBoot默认的异步任务线程池。
 * @date 2024/5/20
 */
@AutoConfiguration
@ConfigurationProperties(prefix = "com.hangjizhe.thread.async.pool")
@Data
public class TaskExecutorConfiguration implements AsyncConfigurer {

    /**
     * 获取当前机器的核数, 不一定准确 请根据实际场景 CPU密集 || IO 密集
     */
    public static final int cpuNum = Runtime.getRuntime().availableProcessors();

    private Optional<Integer> corePoolSize;

    private Optional<Integer> maxPoolSize;

    private Optional<Integer> queueCapacity;

    private Optional<Integer> awaitTerminationSeconds;

    @Override
    @Bean
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程大小 默认区 CPU 数量
        taskExecutor.setCorePoolSize(corePoolSize.orElse(cpuNum));
        // 最大线程大小 默认区 CPU * 2 数量
        taskExecutor.setMaxPoolSize(maxPoolSize.orElse(cpuNum * 2));
        // 队列最大容量
        taskExecutor.setQueueCapacity(queueCapacity.orElse(500));
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(awaitTerminationSeconds.orElse(60));
        taskExecutor.setThreadNamePrefix("HangJiZhe-Thread-");
        taskExecutor.initialize();
        return taskExecutor;
    }

}

Executors工具类

不建议使用,快速测试可以使用。实际上是提供的静态方法,内部封装的实际为ThreadPoolExecutor构造的创建方式。

代码其实很简单,使用Executors调用其方法即可。eg:

        try (ExecutorService executorService = Executors.newCachedThreadPool();) {
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                executorService.execute(() -> {
                    System.out.println("i = " + finalI);
                    try {
                        ThreadUtil.sleep(Duration.ofSeconds(1));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("结束 i  = " + finalI);
                });
            }
        }

Async

springboot默认的异步任务线程池见:

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

推荐自定义异步任务线程池,可以自定义线程池线程名字的前缀,自定义方式见上述的“ SpringBoot中线程池”。

以下案例包含:

1、启用异步任务的注解。

2、错误使用@Async的方法。

3、带有返回值@Async的方法。

4、调用@Async的方法案例。

@EnableAsync
public class AppThreadApplication {}


    @Async
    // 调用会报错。
    public String doBizAsyncAndGetResult(String name) {
        log.info("doBizAsync:name:{}", name);
        try {
            ThreadUtil.sleep(Duration.ofSeconds(2));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String uuid = UUID.randomUUID().toString();
        log.info("doBizAsync:uuid:{}", uuid);
        return uuid;
    }


    @Async
    public CompletableFuture<String> doBizAsyncAndGetResult2(String name, int seconds) {
        log.info("doBizAsync:name:{}", name);
        try {
            ThreadUtil.sleep(Duration.ofSeconds(seconds));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String uuid = UUID.randomUUID().toString();
        log.info("doBizAsync:uuid:{}", uuid);
        return CompletableFuture.completedFuture(uuid);
    }


// 主体函数调用:
    @GetMapping("/hello5")
    public R<String> hello5(String name, int seconds) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> resultFuture = asyncService.doBizAsyncAndGetResult2(name, seconds)
                .completeOnTimeout(null, 3, TimeUnit.SECONDS);
        String result = resultFuture.get();
//        String result = resultFuture.get(3, TimeUnit.SECONDS);
        log.info("doBizAsyncAndGetResult hello5 result = " + result);
        log.info("doBizAsyncAndGetResult hello5 = " + name);
        return R.ok("hello5:" + name + ";result:" + result);
    }

CompletableFuture

用于支持异步线程和异步结果处理类,她包含很多对象方法和静态方法。静态方法多用于组合编排多个异步线程。

方法太多了,只列一下常用的,

常用对象方法:
  • get(): 阻塞直到计算完成,获取到结果。
  • get(long timeout, TimeUnit unit):跟get类型,可以设置超时时间,如果超时,则抛出TimeoutException。(建议使用对象方法: completeOnTimeout())
  • completeOnTimeout(T value, long timeout, TimeUnit unit): 方法可以设置一个结果,但是这个结果是在一定的时间之后设置的。(到时间后,直接将结果返回,但是原本的线程不会被中断;可以通过关闭或者异常停止任务。)
  • exceptionally():当任务发生异常时执行 Function 处理异常,返回一个默认值或者执行备选操作。
常用静态方法:
  • supplyAsync:创建异步执行一个 Supplier 任务,并返回 CompletableFuture 对象,可以获取异步计算的结果。
  • runAsync:创建异步执行一个 Runnable 任务,返回 CompletableFuture<Void>,适合于不需要返回值的异步操作。
  • anyOf(): 当任意一个 CompletableFuture 完成时(正常完成、异常或取消),返回一个新的 CompletableFuture<Object>。
  • allOf():当所有 CompletableFuture 完成时(正常完成、异常或取消),返回一个新的 CompletableFuture<Void>。
案例:
package com.fagejiang.appthread.completable;

import com.fagejiang.appthread.utils.ThreadPool;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * @program: fa-cloud
 * @description: 异步线程编排
 * @author: <发哥讲Java-694204477@qq.com>
 * @create: 2024-06-20 14:07
 **/
@Slf4j
public class CompletableFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Random random = new Random();
        // 定义执行线程所需时间。
        int c1s = random.nextInt(10);
        int c2s = random.nextInt(10);
        log.info("c1s:{}", c1s);
        log.info("c2s:{}", c2s);

        // 定义固定线程执行等待时间,单位秒,等待3秒。
        int timeout = 3;

        // 定义 兜底 结果。
        String c1r = " c1r 兜底 ";
        String c2r = " c2r 兜底 ";

        CompletableFuture<String> c1 = CompletableFuture.supplyAsync(() -> {
            UUID uuid = UUID.randomUUID();
            log.info("c1 执行开始....,uuid:{}", uuid);
            sleepAndCatchException(Duration.ofSeconds(c1s));
            log.info("c1 执行结束,uuid:{}", uuid);
            return uuid.toString();
        }, ThreadPool.threadPool).exceptionally((e) -> {
            UUID uuid = UUID.randomUUID();
            log.error("c1  error:{} , new UUID:{}", e.toString(), uuid);
            return uuid.toString();
        }).completeOnTimeout(c1r, timeout, TimeUnit.SECONDS);
        CompletableFuture<String> c2 = CompletableFuture.supplyAsync(() -> {
            UUID uuid = UUID.randomUUID();
            log.info("c2 执行开始....,uuid:{}", uuid);
            sleepAndCatchException(Duration.ofSeconds(c2s));
            log.info("c2 执行结束,uuid:{}", uuid);
            return uuid.toString();
        }, ThreadPool.threadPool).exceptionally((e) -> {
            UUID uuid = UUID.randomUUID();
            log.error("c2  error:{} , new UUID:{}", e.toString(), uuid);
            return uuid.toString();
        }).completeOnTimeout(c2r, timeout, TimeUnit.SECONDS);

        // 所有任务执行成功。
//        CompletableFuture.allOf(c1, c2);
//        String result1 = c1.get();
//        String result2 = c2.get();
//        log.info("result1 : {}", result1);
//        log.info("result2 : {}", result2);

        // 任一任务执行成功
        CompletableFuture.anyOf(c1, c2);
        String result1 = c1.get();
        String result2 = c2.get();
        log.info("result1 : {}", result1);
        log.info("result2 : {}", result2);

    }

    public static void sleepAndCatchException(Duration duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

执行效果:

allof:

 anyof:

XXL-JOB

XXL-JOB集成实际很简单。

首先理解XXL-JOB架构和集成到自己项目中的架构。

简单理解就是:

1、首先部署官方XXL-JOB的admin模块。

        这个模块包含了管理界面和调度管理,导入sql,修改数据库地址,修改端口啥的。

2、其次是集成到项目中,作为执行期(被调度的模块)

        将XXL-CORE集成到项目中,修改配置文件,调整admin注册的位置,端口号啥的。

很简单。

参考案例:见 xxl-job/xxl-job-executor-samples at master · xuxueli/xxl-job · GitHub

官方文档地址:https://www.xuxueli.com/xxl-job/

GitHub:https://github.com/xuxueli/xxl-job

目前根据GitHub上的提交情况,xxl大神还是在维护基础版本的(比如,jdk,springboot等)

发哥这边目前(2024年06月)使用JDK-21,SpringBoot3.3,集成XXL_JOB遇到小坑,主要是配置文件中属性名称路径调整了,另外是前端界面前后端表达式调整了。

(解决升级boot3 后 Freemarker 取消全局参数 ${Request} 的问题:升级到Java 17,spring boot 3.x后xxl-job页面报错_xxl-job jdk17-CSDN博客

面试题

线程池ThreadPoolExecutor的参数都有什么?

详细见正文的线程池创建方式..

包含7大参数。

关于我

一个热爱技术、向往生活的编程小人物,致力于通过博客CSDN和公众号《航迹者》,与广大读者分享我的技术见解和开发经验。联系我:CSDN、公众号《航迹者》,wx:AiHangjizhe。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

航迹者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值