使用线程池异步执行定时任务


前言

最近项目中需要做一个定时任务在某个固定的时间去执行一个任务,由于该定时任务的代码量已经超出可读性规范,并且处于性能的考虑,故使用线程池来做一个异步执行的方法


一、需求分析

产品给到的需求是,让用户获得的能量根据有效期的规则配置,去实现一个能量过期的效果,因为是springboot单体项目,使用XXLJOB就过于繁琐,而且项目中没有其它的定时任务,私以为使用Scheduled执行比较简单,易用。然后编写过程中发现,代码行数太多,按照不能超过80行的规范,故将一部分业务拆分出去,并使用线程池异步执行。

二、使用步骤

1.配置线程池

代码如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;


@Configuration
@EnableAsync//开启异步
public class ThreadPoolTaskConfig {
    @Bean("threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(16);
        executor.setQueueCapacity(1024);
        executor.setMaxPoolSize(64);
        executor.setKeepAliveSeconds(30);
        executor.setThreadNamePrefix("thread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

犹豫还要考虑性能安全的问题,所以使用Redisson,做一个锁


import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private Integer port;

    @Bean
    public RedissonClient getRedisson() throws Exception {
        RedissonClient redisson = null;

        Config config = new Config();
        //.setPassword(password)
        config.useSingleServer().setAddress("redis://" + host + ":" + port);
        redisson = Redisson.create(config);
        System.out.println(redisson.getConfig());

        return redisson;
    }
}

需要注意的是,其中的@Value已经在yml配置文件中配置好了,这里不再记录

2.编写能量过期方法

代码如下:

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hsbc.component.AddOutEnergyComponent;
import com.hsbc.enums.EnergyType;
import com.hsbc.mapper.EnergyRecordMapper;
import com.hsbc.model.EnergyRecord;
import com.hsbc.model.EnergyRule;
import com.hsbc.service.IEnergyRecordsService;
import com.hsbc.service.IEnergyRuleService;
import com.hsbc.enums.DelFlagEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;


@Configuration
@EnableScheduling//开启定时任务
@Slf4j
public class EneryOutTask {

    @Autowired
    private EnergyRecordMapper energyRecordMapper;

    @Resource
    private RedissonClient redissonClient;
    @Autowired
    private AddOutEnergyComponent addOutEnergyComponent;

    public static final String ENERGY_OUT_LOCK_KEY = "energy:out:lock:%s";

    /**
     * 定时任务
     * 每月月初00:00:00执行
     * 已过期的能量处理
     */
    @Scheduled(cron = "0 0 0 1 * ?")
    public void outEnergy() {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String format = sdf.format(date);
        String accessLock = String.format(ENERGY_OUT_LOCK_KEY,format);
        try {
            RLock lock = redissonClient.getLock(accessLock);
            boolean isLock = lock.tryLock(-1, 100, TimeUnit.SECONDS);
            if (isLock) {
                try {
                    /*获取到本月的最后时间*/
                    Calendar calendar = Calendar.getInstance();
                    calendar.add(Calendar.MONTH, -1);
                    calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
                    calendar.set(Calendar.HOUR_OF_DAY, calendar.getActualMaximum(Calendar.HOUR_OF_DAY));
                    calendar.set(Calendar.MINUTE, calendar.getActualMaximum(Calendar.MINUTE));
                    calendar.set(Calendar.SECOND, calendar.getActualMaximum(Calendar.SECOND));
                    calendar.set(Calendar.MILLISECOND, calendar.getActualMaximum(Calendar.MILLISECOND));
                    QueryWrapper<EnergyRecord> queryWrapper = new QueryWrapper<>();
                    queryWrapper.le("validity_time", calendar.getTime());
                    queryWrapper.eq("del_flag", DelFlagEnum.NO_DEL.getCode());
                    /*过期和代扣的能量不统计*/
                    queryWrapper.ne("type", EnergyType.REDUCE_ENERY.getCode());
                    queryWrapper.ne("type", EnergyType.EXPIRE.getCode());
                    /*所有到期的能量*/
                    List<EnergyRecord> energyRecords = energyRecordMapper.selectList(queryWrapper);
                    Map<String, Integer> outEnergyMap = new HashMap<>(energyRecords.size()); //会员即将到期的能量map
                    Map<String, List<String>> outEnergyIdsMap = new HashMap<>(); //全部到期准备删除的能量id
                    for (EnergyRecord energyRecord : energyRecords) {

                        Integer outEnergy = outEnergyMap.get(energyRecord.getMemberId());
                        if (outEnergy == null) {
                            outEnergy = 0;
                        }
                        outEnergy += energyRecord.getResidualEnergy();
                        outEnergyMap.put(energyRecord.getMemberId(), outEnergy);
                        List<String> list = outEnergyIdsMap.get(energyRecord.getMemberId());
                        if (list == null) {
                            list = new ArrayList<>();
                        }
                        list.add(energyRecord.getId());
                        outEnergyIdsMap.put(energyRecord.getMemberId(), list);
                    }
                    for (Map.Entry<String, Integer> entry : outEnergyMap.entrySet()) {
                        /*异步添加*/
                        addOutEnergyComponent.add(entry.getKey(), entry.getValue(), outEnergyIdsMap.get(entry.getKey()));
                    }
                } finally {
                    log.info("操作业务完毕,开始释放锁");
                    lock.unlock();
                }
            } else {
                log.info("当前过期能量定时任务已被执行!!");
            }
        } catch (Exception e) {
            log.error("已过期能量定时任务报错", e);
        }
    }
}


3.编写异步执行方法

代码如下:

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hsbc.enums.DelFlagEnum;
import com.hsbc.enums.EnergyReadEnum;
import com.hsbc.enums.EnergyType;
import com.hsbc.mapper.EnergyRecordMapper;
import com.hsbc.model.EnergyRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;

@Component
@Slf4j
public class AddOutEnergyComponent {

    @Autowired
    private EnergyRecordMapper energyRecordMapper;
    //配置线程池
    @Async("threadPoolTaskExecutor")
    public void add(String memberId, Integer energy, List<String> outIds) {
        QueryWrapper<EnergyRecord> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("member_id", memberId);
        queryWrapper.orderByDesc("id");
        Page<EnergyRecord> page = new Page<>(1, 1);
        Page<EnergyRecord> energyRecordPage = energyRecordMapper.selectPage(page, queryWrapper);
        int lastEnergy = 0;
        if (CollectionUtils.isNotEmpty(energyRecordPage.getRecords())) {
            EnergyRecord energyRecord = energyRecordPage.getRecords().get(0);
            lastEnergy = energyRecord.getBeforeEnergy() + energyRecord.getEnergyNumber();
        }
        EnergyRecord energyRecord = new EnergyRecord();
        energyRecord.setMemberId(memberId);
        energyRecord.setBeforeEnergy(lastEnergy);
        energyRecord.setType(EnergyType.EXPIRE.getCode());
        energyRecord.setEnergyNumber(-energy);
        energyRecord.setCreateTime(new Date());
        energyRecord.setAdditional("能量到期");
        energyRecord.setStatus(EnergyReadEnum.READ.getCode());
        energyRecord.setDelFlag(DelFlagEnum.DEL.getCode());
        energyRecordMapper.insert(energyRecord);
        //修改过期能量为已删除
        energyRecordMapper.update(null,new UpdateWrapper<EnergyRecord>().in("id",outIds).set("del_flag",DelFlagEnum.DEL));
    }
}

其中该需求是在能量过期以后,在能量记录表中添加数据,所以涉及另外一张表的数据插入操作,至此已经完全实现需求的功能

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 (2)建模简单:通过使用线程可以讲复杂并且异步的工作流进一步分解成一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置交互 (3)简化异步事件的处理:服务器应用程序在接受来自多个远程客户端的请求时,如果为每个连接都分配一个线程并且使用同步IO,就会降低开发难度 (4)用户界面具备更短的响应时间:现代GUI框架中大都使用一个事件分发线程(类似于中断响应函数)来替代主事件循环,当用户界面用有事件发生时,在事件线程中将调用对应的事件处理函数(类似于中断处理函数) 线程的风险 线程安全性:永远不发生糟糕的事情 活跃性问题:某件正确的事情迟早会发生 问题:希望正确的事情尽快发生 服务时间过长 响应不灵敏 吞吐率过低 资源消耗过高 可伸缩性较低 线程的应用场景 Timer 确保TimerTask访问的对象本身是线程安全的 Servlet和JSP Servlet本身要是线程安全的 正确协同一个Servlet访问多个Servlet共享的信息 远程方法调用(RMI) 正确协同多个对象中的共享状态 正确协同远程对象本身状态的访问 Swing和AWT 事件处理器与访问共享状态的其他代码都要采取线程安全的方式实现 框架通过在框架线程中调用应用程序代码将并发性引入应用程序,因此对线程安全的需求在整个应用程序中都需要考虑 基础知识 线程安全性 定义 当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的 无状态对象一定是线程安全的,大多数Servlet都是无状态的 原子性 一组不可分割的操作 竞态条件 基于一种可能失效的观察结果来做出判断或执行某个计算 复合操作:执行复合操作期间,要持有锁 锁的作用 加锁机制、用锁保护状态、实现共享访问 锁的不恰当使用可能会引起程序性能下降 对象的共享使用策略 线程封闭:线程封闭的对象只能由一个线程拥有并修改 Ad-hoc线程封闭 栈封闭 ThreadLocal类 只读共享:不变对象一定是线程安全的 尽量将域声明为final类型,除非它们必须是可变的 分类 不可变对象 事实不可变对象 线程安全共享 封装有助于管理复杂度 线程安全的对象在其内部实现同步,因此多个接口可以通过公有接口来进行访问 保护对象:被保护的对象只能通过特定的锁来访问 将对象封装到线程安全对象中 由特定锁保护 保护对象的方法 对象的组合 设计线程安全的类 实例封闭 线程安全的委托 委托是创建线程安全类的最有效策略,只需要让现有的线程安全类管理所有的状态 在现有线程安全类中添加功能 将同步策略文档化 基础构建模块 同步容器类 分类 Vector Hashtable 实现线程安全的方式 将状态封装起来,对每个公有方法都进行同步 存在的问题 复合操作 修正方式 客户端加锁 迭代器 并发容器 ConcurrentHashMap 用于替代同步且基于散列的Map CopyOnWriteArrayList 用于在遍历操作为主要操作的情况下替代同步的List Queue ConcurrentLinkedQueue *BlockingQueue 提供了可阻塞的put和take方法 生产者-消费者模式 中断的处理策略 传递InterruptedException 恢复中断,让更高层的代码处理 PriorityQueue(非并发) ConcurrentSkipListMap 替代同步的SortedMap ConcurrentSkipListSet 替代同步的SortedSet Java 5 Java 6 同步工具类 闭锁 *应用场景 (1)确保某个计算在其需要的所有资源都被初始化后才能继续执行 (2)确保某个服务在其所依赖的所有其他服务都已经启动之后才启动 (3)等待知道某个操作的所有参与者都就绪再继续执行 CountDownLatch:可以使一个或多个线程等待一组事件发生 FutureTask *应用场景 (1)用作异步任务使用,且可以使用get方法获取任务的结果 (2)用于表示一些时间较长的计算 状态 等待运行 正在运行 运行完成 使用Callable对象实例化FutureTask类 信号量(Semaphore) 用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量 管理者一组虚拟的许可。acquire获得许可(相当于P操作),release释放许可(相当于V操作) 应用场景 (1)二值信号量可用作互斥体(mutex) (2)实现资源池,例如数据库连接池 (3)使用信号量将任何一种容器变成有界阻塞容器 栅栏 能够阻塞一组线程直到某个事件发生 栅栏和闭锁的区别 所有线程必须同时到达栅栏位置,才能继续执行 闭锁用于等待事件,而栅栏用于等待线程 栅栏可以重用 形式 CyclicBarrier 可以让一定数量的参与线程反复地在栅栏位置汇集 应用场景在并行迭代算法中非常有用 Exchanger 这是一种两方栅栏,各方在栅栏位置上交换数据。 应用场景:当两方执行不对称的操作(读和取) 线程池 任务与执行策略之间的隐形耦合 线程饥饿死锁 运行时间较长的任务 设置线程池的大小 配置ThreadPoolExecutor 构造参数 corePoolSize 核心线程数大小,当线程数= corePoolSize的时候,会把runnable放入workQueue中 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了” keepAliveTime 保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。 workQueue 保存任务的阻塞队列 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务 threadFactory 创建线程的工厂 handler 拒绝策略 unit 是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值 线程的创建与销毁 管理队列任务 饱和策略 AbortPolicy DiscardPolicy DiscardOldestPolicy CallerRunsPolicy 线程工厂 在调用构造函数后再定制ThreadPoolExecutor 扩展 ThreadPoolExecutor afterExecute(Runnable r, Throwable t) beforeExecute(Thread t, Runnable r) terminated 递归算法的并行化 构建并发应用程序 任务执行 在线程中执行任务 清晰的任务边界以及明确的任务执行策略 任务边界 大多数服务器以独立的客户请求为界 在每个请求中还可以发现可并行的部分 任务执行策略 在什么(What)线程中执行任务? 任务按照什么(What)顺序执行(FIFO、LIFO、优先级)? 有多少个(How Many)任务能并发执行? 在队列中有多少个(How Many)任务在等待执行? 如果系统由于过载而需要拒绝一个任务,那么应该选择哪一个(Which)任务?另外,如何(How)通知应用程序有任务被拒绝? 在执行一个任务之前或之后,应该进行什么(What)动作? 使用Exector框架 线程池 newFixedThreadPool(固定长度的线程池) newCachedThreadPool(不限规模的线程池) newSingleThreadPool(单线程线程池) newScheduledThreadPool(带延迟/定时的固定长度线程池) 具体如何使用可以查看JDK文档 找出可利用的并行性 某些应用程序中存在比较明显的任务边界,而在其他一些程序中则需要进一步分析才能揭示出粒度更细的并行性 任务的取消和关闭 任务取消 停止基于线程的服务 处理非正常的线程终止 JVM关闭 线程池的定制化使用 任务和执行策略之间的隐性耦合 线程池的大小 配置ThreadPoolExecutor(自定义的线程池) 此处需要注意系统默认提供的线程池是如何配置的 扩展ThreadPoolExector GUI应用程序探讨 活跃度(Liveness)、性能、测试 避免活跃性危险 死锁 锁顺序死锁 资源死锁 动态的锁顺序死锁 开放调用 在协作对象之间发生的死锁 死锁的避免与诊断 支持定时的显示锁 通过线程转储信息来分析死锁 其他活跃性危险 饥饿 要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。 糟糕的响应性 如果由其他线程完成的工作都是后台任务,那么应该降低它们的优先级,从而提高前台程序的响应性。 活锁 要解决这种活锁问题,需要在重试机制中引入随机性(randomness)。为了避免这种情况发生,需要让它们分别等待一段随机的时间 性能与可伸缩性 概念 运行速度(服务时间、延时) 处理能力(吞吐量、计算容量) 可伸缩性:当增加计算资源时,程序的处理能力变强 如何提升可伸缩性 Java并发程序中的串行,主要来自独占的资源锁 优化策略 缩
## 项目功能 1. 架构潮流:系统采用SpringBoot+VUE前后端分离,前端单独部署,Nginx负载均衡 2. 接口友好:同时支持swagger2、knife4j两种可视化接口API调试,支持离线接口文档; 3. 任务管理:支持通过Web页面对任务进行CRUD操作,可视化界面,快速上手; 4. 执行记录:支持通过web页面在线查看调度结果、执行结果、下次执行时间; 5. 实时日志:支持通过web页面实时查看执行器输出的完整的执行日志; 6. 唯一搜索:支持通过web界面根据jobname或jobgroup进行全局唯一查询 7. 强自定义:支持在线配置定时任务请求类型、请求路径、请求参数、Cron表达式,即时生效; 8. 动态控制:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效; 9. 执行策略:支持丰富的执行策略,包括:Get请求、PostJson请求、PostFrom表单请求; 10. 自动注册:周期性自动注册任务, 同时,也支持手动录入定时任务地址; 11. 自动执行:系统会自动发现注册的任务并触发执行,同时,也支持手动触发-立即执行; 12. 用户管理:支持在线管理系统用户、角色、菜单,默认管理员、开发者、普通用户三种角色; 13. 权限控制:支持在线权限控制,管理员拥有全量权限,开发者拥有除角色管理外的所有权限,普通用户仅支持任务管理相关权限; 14. 集群部署:支持分布式执行,系统支持集群部署,可保证任务执行的高可用; 15. 弹性调度:一旦有任务机器上线或者下线,下次调度时将会重新分配任务; 16. 路由策略:系统集群部署时提供丰富的路由策略,包括:轮询、随机、故障转移、忙碌转移等常用策略; 17. 故障转移:任务路由策略选择"故障转移"情况下,如果集群中某一台机器故障,将会自动切换到一台正常的执行器发送调度请求; 18. 阻塞策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; 19. 超时控制:支持自定义任务超时时间,任务运行超时将会主动中断任务; 20. 重试机制:支持自定义任务重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试; 21. 消息工厂:默认提供邮件工厂的方式推送消息,同时预留扩展接口,可方便的扩展短信、钉钉等消息方式; 22. 邮件告警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; 23. 运行报表:支持实时查看运行数据,以及调度报表,如调度日期分布图,任务组执行比例比例分布图等; 24. 事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,提供触发任务单次执行的API服务; 25. 脚本任务:支持以GLUE分布式平台开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本; 26. 多线并发:系统支持多线程触发调度运行,确保调度精确执行,不被堵塞; 27. 降级隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性; 28. Gradle: 将会把最新稳定版推送到gradle中央仓库, 方便用户接入和使用; 29. Maven: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; 30. 一致性:基于Redis分布式锁保证集群分布式调度的最终一致性, 一次任务调度只会触发一次执行; 31. 全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行; 32. 跨语言:系统提供语言无关的 RESTFUL API 服务,第三方任意语言可据此对接Task Manage; 33. 国际化:后管系统支持国际化设置,提供中文、英文两种可选语言,默认为中文; 34. 容器化:提供官方docker镜像,并实时更新推送dockerhub,进一步实现产品开箱即用; ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
解决线程的死掉问题和超时问题特别好使,在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现。 Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式的实 现,可以来进行异步计算。 Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时 间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future 接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。 Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程 序执行超时的关键。 Future接口是一个泛型接口,严格的格式应该是Future,其中V代表了Future执行的任务返回值的类型。 Future接口的方法介绍如下: boolean cancel(boolean mayInterruptIfRunning) 取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束 boolean isCancelled() 任务是否已经取消,任务正常完成前将其取消,则返回true boolean isDone() 任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true V get() throws InterruptedException, ExecutionException 等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计 算超时,将抛出TimeoutException Future的实现类有java.util.concurrent.FutureTask即 javax.swing.SwingWorker。通常使用FutureTask来处理我们的任务。FutureTask类同时又 实现了Runnable接口,所以可以直接提交给Executor执行使用FutureTask实现超时执行的代码如附件:FutureTaskAndExcutor.java 不直接构造Future对象,也可以使用ExecutorService.submit方法来获得Future对象,submit方法即支持以 Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性。使用示例如FutureTaskAndExcutor中的limitDemo2方法。
后端技术 - 基础框架:Java8 & Spring Boot & Maven - 数据库:Mysql 等 - 鉴权框架:Spring Security OAuth2 - 缓存框架:Redis & Redisson - 持久层框架:Mybatis Plus - 日志记录:Logback - 工作流框架:Flowable - 其他依赖:Lombok、Kaptcha、EasyExcel 等 ### 前端技术 - vue , vuex , vue-router - elementui - vue-element-admin - vue-form-making - mock ## 主要实现内容 1. 前后分离开发,前后端可以独立部署,也可以合并部署 2. `我的流程`、`系统管理`、`示例管理`、`流程管理`、`监控管理` 3. `功能权限`,`菜单权限`、`按钮权限`细粒度配置 4. `数据权限`,注解实现或数据库配置实现 5. `我的流程` 包含 `我的汇总`、`发起流程`、`我的待办`、`我的待阅`、`我的已办`,`流程管理` 包含 `表单设计`、`流程设计`、`流程定义`、`流程管理`、`任务管理`,`任务执行` 包含 `提交`、`抄送`、`转办`、`委派`、`终止`、`退回`(已实现退回并行网关节点、子流程退回)、`(批量)已阅` 等 流程设计约定: - 发起者启动流程后若要自动完成第一个用户任务,则第一个 `userTask` 的 `id` 要定义为`__initiator__`,若涉及流程表单,则可设置`__initiator__`的任务表单 `formKey` 与流程表单相同 - 如果涉及并行网关,并行网关需成对出现,且发散节点要以 `_begin` 结尾,汇聚节点要以 `_end` 结尾,可以嵌套但不能交叉嵌套,这样就能确保可以退回到并行网关的单个节点上(不会退回到并行网关的其他分支) - 如果流程涉及业务主键 `businessKey`,流程设计时加入 `流程扩展属性` 即可 ```xml <extensionElements> <flowable:properties> <flowable:property name="showBusinessKey" value="true" /> </flowable:properties> </extensionElements> ``` - 流程设计可参考 `zjmzxfzhl/zjmzxfzhl-admin/src/main/resources/processes_test` 下的流程 - 自定义表单使用 `MIT` 开源的 [FormGenerator](https://gitee.com/mrhj/form-generator),后续我也会进行更深层次的封装,目前已优化可以支持响应式布局 6. `Redis` 分布式锁,可实现交易防重发等业务场景 7. `App` 开发示例,包含:注册、登录、获取用户信息等 8. 代码生成器,包含前端和后端 9. 定时任务异步任务线程池管理 ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
在 Spring Boot 中,配置线程池执行定时任务可以通过以下步骤实现: 1. 在 Spring Boot 的配置文件中,配置线程池的参数,如下所示: ```yaml spring: task: scheduling: pool: size: 5 ``` 其中,`spring.task.scheduling.pool.size` 表示线程池的大小。 2. 创建一个定时任务使用 `@Scheduled` 注解标注要执行的方法,并使用 `@Async` 注解将任务放在异步线程池执行。例如: ```java @Component public class MyTask { @Async("taskExecutor") @Scheduled(fixedRate = 1000) public void doTask() { // 执行任务 } } ``` 其中,`@Async("taskExecutor")` 表示将任务放在名为 `taskExecutor` 的线程池执行。 3. 在 Spring Boot 中,可以使用 `ThreadPoolTaskExecutor` 类来创建线程池。例如: ```java @Configuration @EnableAsync public class TaskExecutorConfig { @Bean("taskExecutor") public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); return executor; } } ``` 其中,`@EnableAsync` 注解用于开启异步执行。`taskExecutor` 方法返回一个 `ThreadPoolTaskExecutor` 对象,其中设置了线程池的参数。 4. 在 Spring Boot 的启动类上添加 `@EnableScheduling` 注解,开启定时任务。例如: ```java @SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 以上就是在 Spring Boot 中配置线程池执行定时任务的步骤。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值