广告
返回顶部
首页 > 资讯 > 数据库 >SpringBoot异步任务及并行事务实现
  • 953
分享到

SpringBoot异步任务及并行事务实现

javaspringbootmysql 2023-09-05 20:09:59 953人浏览 八月长安
摘要

        上一篇介绍了原生Java如何实现串行/并行任务,主要使用了线程池 + Future + CountDownLatch,让主线程等待子线程返回后再向下进行。而在SpringBoot中,利用@Async和aop对异步任务提供了更

        上一篇介绍了原生Java如何实现串行/并行任务,主要使用了线程池 + Future + CountDownLatch,让主线程等待子线程返回后再向下进行。而在SpringBoot中,利用@Async和aop对异步任务提供了更加便捷的支持,下面就针对springBoot使用异步任务需要注意的细节做一些分析。

1 SpringBoot异步任务基础实现

        使用起来很简单,在启动类或配置类上加上@EnableAsync启动异步任务,并在需要异步调用的方法上加@Async,在注册Bean时就会生成该类的Proxy子类,也就是动态代理类,AOP会在代理类中重写并增强该异步方法。

1.1 配置异步任务线程池

        SpringBoot自然也选择了线程复用,想要实现就需要使用线程池,可以先来看看默认线程池的配置。

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(8);//配置最大线程数executor.setMaxPoolSize(Integer.MAX_VALUE);//配置空闲线程保留时间executor.seTKEepAliveSeconds(60);//配置队列大小executor.setQueueCapacity(Integer.MAX_VALUE);//设置饱和策略:当pool已经达到max size的时候,如何处理新任务executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

        配置过线程池参数的小伙伴,一眼就能看到几个很不合理的点:

  • 最大线程数为Integer.MAX_VALUE,创建线程过多会导致“oom:unable to create new native thread”。
  • 最长队列数为Integer.MAX_VALUE,队列堆积任务过多也会导致oom。
  • 饱和策略为AbortPolicy,队列满了直接抛异常,如果不catch程序直接爆炸。

        综上,我们应该给SpringBoot指定一个线程池,并让异步任务执行时使用他,配置就不赘述直接放在下面。

//自定义Spring默认线程池//ThreadPoolTaskExecutor vs ThreadPoolExecutor ://ThreadPoolTaskExecutor是对ThreadPoolExecutor的进一步封装//ThreadPoolTaskExecutor来源于Spring,ThreadPoolExecutor属于JUC//ThreadPoolTaskExecutor需要声明initialize,ThreadPoolExecutor不需要@Bean("common")public Executor commonExecutorBuild() {    log.info("Common Executor Building Start!");    //ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,    //                         UNIT, LINKED_BLOCKING_QUEUE, new ThreadPoolExecutor.CallerRunsPolicy());    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();    //配置核心线程数    executor.setCorePoolSize(2);    //配置最大线程数    executor.setMaxPoolSize(5);    //配置队列大小    executor.setQueueCapacity(10240);    //配置空闲线程保留时间    executor.setKeepAliveSeconds(60);    //配置线程池中的线程的名称前缀    executor.setThreadNamePrefix("AsyncCommonThread-");    //设置饱和策略:当pool已经达到max size的时候,如何处理新任务    //CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());    //执行初始化    executor.initialize();    return executor;}

1.2 异步方法逻辑编写

        应用场景还是如上篇所说 Java串行/并行任务实现,这次就使程序逻辑更加完善且贴近现实。与Java原生实现大体类似,只是将子任务编写在单独的类与方法中,并标注@Async让其异步调用。

         入参为带有所有用户信息的VO实体类,在Service中将所有属性赋值到对应实体类,然后在主线程中办理银行卡,银行卡办理成功后调用两个子线程分别办理会员/申请信用卡,全部完成后根据sql语句执行结果判断是否注册成功。先来编写一下子线程的逻辑。

    @Override    @Async("common")    public CompletableFuture reGISterUser(BankUserInfo bankUserInfo) {        Integer insert = bankUserMapper.registerUser(bankUserInfo);        try {            log.info(Thread.currentThread().getName() + "running!");            //模拟阻塞3秒            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return CompletableFuture.completedFuture(insert);        //return new AsyncResult(insert);    }

        只需要一步数据库操作,将用户信息插入到库中,为了模拟阻塞操作延时了3秒,信用卡申请的逻辑与其相同。有几点值得注意的地方:

  • 在@Async中指定了使用“common”线程池,也就是我们自行定义的线程池。
  • 异步方法的返回值只能为void或Future的子类,一般会指定返回值为“new AsyncResult(result)”,AsyncResult实现了ListenableFuture,ListenableFuture是Future的子类。
  • 这里将异步方法的返回值设置为了CompletableFuture,可以将其理解为增强版的Future。在上篇文章中,我们采用了两种方式来等待两个子线程完成,其中用Future时使用了自旋循环判断线程是否有返回值。而CompletableFuture提供了操作Future执行的各种情况的api,就比如CompletableFuture.allOf(Thread1, Thread2, ...),该方法可以在传入的子线程执行完前,阻塞当前线程,下面我们就就会用到。还有其他很强大的API,以后用到了再介绍。

        下面是主方法逻辑,分别实现3个逻辑,CompletableFuture.allOf(future, future1)在子线程执行完毕前阻塞主线程。最后其实应该加上根据子线程执行结果,给用户展示是否办理成功的,偷了个懒没写,懂意思就行。

    @Override    @Transactional(rollbackFor = Exception.class)    public Integer applyBankCard(BankRegisterVo bankRegisterVo) throws InterruptedException {        String UUID = IdWorker.get32UUID().substring(16);        bankRegisterVo.setBankCardNum(UUID);        BankCardInfo bankCardInfo = new BankCardInfo();        BankUserInfo bankUserInfo = new BankUserInfo();        CreditCardInfo creditCardInfo = new CreditCardInfo();        BeanUtils.copyProperties(bankRegisterVo, bankCardInfo);        BeanUtils.copyProperties(bankRegisterVo, bankUserInfo);        BeanUtils.copyProperties(bankRegisterVo, creditCardInfo);        creditCardInfo.setMoneyLimit(10000.00);        //主线程执行,办理银行卡        Thread.sleep(5000);        int insert = bankCardMapper.applyBankCard(bankCardInfo);        //会导致死锁!!!        //Thread.currentThread().join();        //子线程执行,申请会员 + 信用卡办理        CompletableFuture future = asyncBankService.registerUser(bankUserInfo);        CompletableFuture future1 = asyncBankService.applyCreditCard(creditCardInfo);        CompletableFuture.allOf(future, future1);        //Integer insert1 = future.join();        //Integer insert2 = future1.join();        //HashMap objectObjectHashMap = Maps.newHashMapWithExpectedSize(6);        return insert;    }

1.3 @Async与@Transactional失效

       这两个问题的出现其实是由于一个原因,Spring中的注解基本都是靠AOP来增强,实现原理就是在调用@Async的方法时,实际是在调用该方法的代理类,代理类中将该方法的执行逻辑提交给了线程池。出错的情景一般都如下面这段伪代码。

{    Method1() {        AsyncMethod1();        AsyncMethod2();    }        @Async    @Transactional    AsyncMethod1() {        //Todo...    }      @Async    @Transactional    AsyncMethod2() {        //Todo...    }  }

        在同一个类中调用异步方法,等于调用this本类的方法,没有走Spring生成的代理类,也就不会让他异步执行,@Transactional的原理也类似。

2 异步事务管理

         尝试思考这样一个问题,现在有1个主线程事务 + 2个子线程事务,我们现在要保证他们仨的强原子性——即3个事务有任何一个报错,都会回滚所有事务。最简单的想法可能就是给1主2子都加上@Transactional注解,但这样实际是行不通的,子线程的异常只会回滚他自身事务。

        举个例子,子线程办理会员报错回滚,并不会影响没有报错的主线程银行卡办理和另一个子线程申请信用卡。事实也确实如此,在实际测试中,用户信息表插入失败回滚,银行卡信息表与信用卡信息表仍然会正常插入记录。

        如果有这样的强原子性场景存在,我们可以将代码逻辑改为串行,放在一个方法体中,这样具有天然原子性了。但这明显与预期不符,有些舍本逐末了。

        终极诉求就是灵活管理多个线程的事务,这时就要用到编程式事务。

2.1 编程式事务的基本使用

        需要注入两个Bean:

  1. TransactionDefinition,其中规定了一些事务的相关属性,例如事务的传播行为和隔离等级等。
  2. DataSourceTransactionManager,JDBC对应的事务管理器。

        将TransactionDefinition传入DataSourceTransactionManager中,就可以手动进行事务管理了,主要用到commit()和rollback()来对应提交和回滚。与声明式事务不同,在catch到异常后我们要手动进行回滚,如果全部正常执行,也需要自行提交事务。

2.2 多线程手动事务管理

        回到我们的需求,实际上是一个多线程手动事务管理的问题,经过分析后我们得到一个程序运行流程图,主要的难点在于如何让3个线程彼此等待,并根据一个统一的标志位判断是否回滚。

        既然是多线程,那就要在JUC里好好挖掘一下。

        首先是标志位,可以用AtomicInteger原子类,保证多线程下的数据一致。我们在主线程中初始化一个值为0的AtomicInteger并传给子线程,任何线程捕获到异常时就给AtomicInteger自增,全部线程执行完成后统一判断标志位是否大于0,如果大于0则全部回滚。

        之后是线程同步判断结果,由于主线程和子线程数量是已知的,可以用计数器CountDownLatch来实现,主线程计数器设为1,子线程计数器设为2。主计数器用于控制整个程序的运行,在所有线程执行完毕前,将程序阻塞在统一判断执行结果的前一步;子计数器用于告知主线程,各子线程是否执行完毕,未执行完毕就阻塞主线程。

        打个比方,现在有一个老大和两个小弟,老大坐在办公室等着小弟汇报工作结果,等两个小弟都告诉他“我干完了哈”之后,老大根据大家的工作成果判断“OK了,大伙可以去吃饭了”或者是“干得是啥啊,全部重做”;这时小弟再根据老大的回应,决定吃饭还是重做。

        首先将主计数器和子计数器都传入子线程中,主线程调用子计数器的await(),在子线程SQL执行结束、并调用countDown()以前会一直阻塞主线程。在子线程中调用主计数器的await(),在所有子线程SQL执行完毕后,主线程向下执行,并对主计数器调用countDown()。这样就实现了所有子线程SQL执行完以前,子线程会阻塞(因为主线程还在阻塞,主计数器未清零);所有线程子线程SQL执行完毕后,主线程、子线程都向下执行,统一判断事务执行标志位。下面用伪代码实现一下。

ThreadMain() {    //主计数器和子计数器    latchMain = countDownLatch(1);    latchSlave = countDownLatch(2);        //Todo:主线程SQL执行...    //启动子线程    ThreadSlave1(latchMain, latchSlave);    ThreadSlave2(latchMain, latchSlave);    //等待所有子线程SQL执行完毕    latchSlave.await();    //所有子线程SQL执行完毕后,主线程执行计数器-1,此时计数器清零,所有线程同步向下进行    latchMain.countDown();    latchMain.await();    //Todo:AtomicInteger判断逻辑,决定所有事务提交/回滚}@AsyncThreadSlave1(latchMain, latchSlave) {    //Todo:子线程SQL执行...    //子线程SQL逻辑执行完后,子计数器-1    latchSlave.countDown();    //等待其他线程执行结果    latchMain.await();        //Todo:AtomicInteger判断逻辑,决定所有事务提交/回滚}@AsycnThreadSlave2(latchMain, latchSlave) {    //Todo:子线程SQL执行...    //子线程SQL逻辑执行完后,子计数器-1    latchSlave.countDown();    //等待其他线程执行结果    latchMain.await();        //Todo:AtomicInteger判断逻辑,决定所有事务提交/回滚}

        如上面伪代码所示,子计数器清零后,主计数器也会清零,此时所有线程会同步进行事务的判断环节。当然还需要完善一下,当任意线程catch到SQL执行异常后,也需要处理对应的计数器,否则会导致线程永久阻塞。

        下面是主Service代码,有完整的手动事务管理、标志位使用、统一判断逻辑。

@Override//手动管理事务//@Transactional(rollbackFor = Exception.class)public Integer applyBankCard(BankRegisterVo bankRegisterVo) {    TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);    AtomicInteger atomicInteger = new AtomicInteger(0);    CountDownLatch latchMain = new CountDownLatch(1);    CountDownLatch latch = new CountDownLatch(2);    Integer result = null;    try {        String UUID = IdWorker.get32UUID().substring(16);        bankRegisterVo.setBankCardNum(UUID);        BankCardInfo bankCardInfo = new BankCardInfo();        BankUserInfo bankUserInfo = new BankUserInfo();        CreditCardInfo creditCardInfo = new CreditCardInfo();        BeanUtils.copyProperties(bankRegisterVo, bankCardInfo);        BeanUtils.copyProperties(bankRegisterVo, bankUserInfo);        BeanUtils.copyProperties(bankRegisterVo, creditCardInfo);        creditCardInfo.setMoneyLimit(10000.00);        //主线程执行,办理银行卡        int insert = bankCardMapper.applyBankCard(bankCardInfo);        //子线程执行,申请会员 + 信用卡办理        CompletableFuture future = asyncBankService.registerUser(bankUserInfo, latchMain, latch, atomicInteger);        CompletableFuture future1 = asyncBankService.applyCreditCard(creditCardInfo, latchMain, latch, atomicInteger);        latch.await();        latchMain.countDown();        latchMain.await();        if (atomicInteger.get() > 0) {            log.info("子线程事务报错,开始回滚");            transactionManager.rollback(transaction);            result = AppHttpCodeEnum.BANK_REGISTER_FAILED.getCode();        } else {            //手动提交            transactionManager.commit(transaction);            result = AppHttpCodeEnum.BANK_REGISTER_SUCCESS.getCode();        }    } catch (Exception e) {        log.info("主线程事务报错,开始回滚");        //手动回滚        transactionManager.rollback(transaction);        atomicInteger.getAndIncrement();        latchMain.countDown();        result = AppHttpCodeEnum.BANK_REGISTER_FAILED.getCode();    }    return result;}

         异步方法逻辑如下。

@Override@Async("common")public CompletableFuture registerUser(BankUserInfo bankUserInfo, CountDownLatch latchMain, CountDownLatch latch, AtomicInteger atomicInteger) {    TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);    Integer insert = null;    try {        log.info(Thread.currentThread().getName() + "running!");        insert = bankUserMapper.registerUser(bankUserInfo);        //模拟阻塞3秒        Thread.sleep(3000);        latch.countDown();        latchMain.await();        if (atomicInteger.get() > 0) {            transactionManager.rollback(transaction);            log.info("子线程事务报错,开始回滚");        } else {            //手动提交            transactionManager.commit(transaction);        }    } catch (Exception e) {        log.info("子线程事务报错,开始回滚");        atomicInteger.getAndIncrement();        //手动回滚        transactionManager.rollback(transaction);        latch.countDown();    }    return CompletableFuture.completedFuture(insert);    //return new AsyncResult(insert);}

        主线程SQL如果报错,子线程方法也就不会开启,直接回滚事务并返回结果。子线程报错,标志位 + 1并回滚事务,其他线程发现标志位不为0,也会主动回滚事务。

2.3 程序执行

        下面来分别测试一下主线程、子线程异常的执行情况。

2.3.1 正常执行

        给接口传入正常的入参,日志正常打印了异步方法中调用的线程名。接口耗时3.29s,异步方法中手动阻塞了3秒,全部正常。

2023-02-09 15:43:59.469  INFO 16660 --- [cCommonThread-1] c.b.service.impl.AsyncBankServiceImpl    : AsyncCommonThread-1running!2023-02-09 15:43:59.481  INFO 16660 --- [cCommonThread-2] c.b.service.impl.AsyncBankServiceImpl    : AsyncCommonThread-2running!

2.3.2 主线程异常

        先将实体类的Validator注释掉,入参的passWord不传,数据库的password字段约束不为null,这样就会执行失败。下面看一下日志。

==>  Preparing: INSERT INTO bank_card_info (bank_card_num, password, bank_name, create_by, create_time) VALUES (?, ?, ?, ?, ?)==> Parameters: 950499ea8b2dc968(String), null, XianBank(String), -1(Long), 2023-02-09 15:51:12.478(Timestamp)Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@222673Da]2023-02-09 15:51:12.577  INFO 428 --- [NIO-6666-exec-6] c.b.s.impl.BankRegisterServiceImpl       : 主线程事务报错,开始回滚

        与预期相符,异步方法还没有调用,主线程直接异常回滚了事务。 

2.3.3 子线程异常

         数据库的username字段约束为不能重复,因此我们传入一个重复的username,让异步线程异常,再来看看结果。

2023-02-09 15:53:01.477  INFO 428 --- [cCommonThread-1] c.b.service.impl.AsyncBankServiceImpl    : AsyncCommonThread-1running!2023-02-09 15:53:01.482  INFO 428 --- [cCommonThread-1] c.b.service.impl.AsyncBankServiceImpl    : 子线程事务报错,开始回滚2023-02-09 15:53:01.484  INFO 428 --- [cCommonThread-2] c.b.service.impl.AsyncBankServiceImpl    : AsyncCommonThread-2running!2023-02-09 15:53:04.490  INFO 428 --- [nio-6666-exec-8] c.b.s.impl.BankRegisterServiceImpl       : 子线程事务报错,开始回滚2023-02-09 15:53:04.493  INFO 428 --- [cCommonThread-2] c.b.service.impl.AsyncBankServiceImpl    : 子线程事务报错,开始回滚

        通过线程名可以看出,子线程1异常后直接回滚,子线程2和主线程得知有线程异常后,也开始回滚。

        SpringBoot的异步基础实现,以及多线程事务控制到这里就介绍完了,下一篇再见哈。

来源地址:https://blog.csdn.net/m0_62375467/article/details/128873924

您可能感兴趣的文档:

--结束END--

本文标题: SpringBoot异步任务及并行事务实现

本文链接: https://www.lsjlt.com/news/395987.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • SpringBoot异步任务及并行事务实现
            上一篇介绍了原生Java如何实现串行/并行任务,主要使用了线程池 + Future + CountDownLatch,让主线程等待子线程返回后再向下进行。而在SpringBoot中,利用@Async和AOP对异步任务提供了更...
    99+
    2023-09-05
    java spring boot mysql
  • SpringBoot 实现异步任务
    异步任务 一、使用背景二、同步任务的优缺点2.1 优点2.2 缺点2.3 总结 三、异步任务的优缺点3.1 优点3.2 缺点3.3 总结 四、Spring Boot 实现异步任务4.0...
    99+
    2023-10-09
    spring boot java spring
  • springboot实现异步任务
    本文实例为大家分享了springboot实现异步任务的具体代码,供大家参考,具体内容如下 1.什么异步任务 同步:一定要等任务执行完了,得到结果,才执行下一个任务。 异步:不等任务执...
    99+
    2022-11-12
  • springboot怎么实现异步任务
    这篇“springboot怎么实现异步任务”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“springboot怎么实现异步任务...
    99+
    2023-06-29
  • springboot定时任务和异步任务怎么实现
    这篇文章主要介绍“springboot定时任务和异步任务怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“springboot定时任务和异步任务怎么实现”文章能帮助大家解决问题。异步任务简单案例...
    99+
    2023-06-27
  • Java SpringBoot @Async如何实现异步任务
    本篇内容介绍了“Java SpringBoot @Async如何实现异步任务”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成...
    99+
    2023-07-04
  • Flask实现异步执行任务
    Flask 是 Python 中有名的轻量级同步 web 框架,在一些开发中,可能会遇到需要长时间处理的任务,此时就需要使用异步的方式来实现,让长时间任务在后台运行,先将本次请求的响...
    99+
    2022-11-12
  • JavaScript事件循环同步任务与异步任务怎么实现
    这篇文章主要介绍“JavaScript事件循环同步任务与异步任务怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“JavaScript事件循环同步任务与异步任务...
    99+
    2022-10-19
  • 详细讲解springboot如何实现异步任务
    目录Spring Boot介绍Spring Boot特点异步任务Spring Boot介绍 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 ...
    99+
    2022-11-13
  • Java SpringBoot @Async实现异步任务的流程分析
    目录1、同步任务2、@Async 异步任务-无返回值3、@Async 异步任务-有返回值4、@Async + 自定义线程池5、CompletableFuture 实现异步任务依赖po...
    99+
    2022-12-27
    SpringBoot @Async异步任务 SpringBoot异步任务
  • JavaScript事件循环同步任务与异步任务实例分析
    本篇内容介绍了“JavaScript事件循环同步任务与异步任务实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!执行栈与任务队列执行栈姑...
    99+
    2023-07-02
  • 详解springboot通过Async注解实现异步任务及回调的方法
    目录前言什么是异步调用?1. 环境准备2. 同步调用3. 异步调用4. 异步回调前言 什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必...
    99+
    2022-11-13
  • Django怎么实现异步任务
    本篇内容介绍了“Django怎么实现异步任务”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! 对于网站来说...
    99+
    2022-10-19
  • python异步任务怎么实现
    在Python中,可以使用协程(Coroutine)和异步IO来实现异步任务。首先,需要使用`async`关键字定义一个协程函数(C...
    99+
    2023-09-22
    python
  • android异步任务怎么实现
    在Android中,可以使用AsyncTask类来实现异步任务。下面是一个简单的示例:1. 创建一个继承自AsyncTask的子类,...
    99+
    2023-08-18
    android
  • springboot为异步任务规划自定义线程池的实现
    目录一、Spring Boot任务线程池二、自定义线程池三、优雅地关闭线程池一、Spring Boot任务线程池 线程池的作用 防止资源占用无限的扩张调用过程省去资源的创建和销毁所占...
    99+
    2022-11-13
  • Django celery实现异步任务操作,并在后台运行(守护进程)
    没废话,直接上代码。 环境说明: python3.6 django2.0.5 我们使用redis的作为celery任务队列,有一个合成包可以直接安装两者一起使用需要的安装包 直接在终...
    99+
    2022-11-11
  • Android中如何实现异步任务
    Android中如何实现异步任务,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。为了解决新线程不能更新UI组件的问题,Android提供了几种解决方案使用HanlderAci...
    99+
    2023-06-04
  • python多进程及通信实现异步任务的方法
    目录一、python多进程及通信基本用法1、多进程的基本实现a、Process重写run方法 b、使用Process和target方法c、直接使用Process类2、多进程...
    99+
    2022-11-11
  • springboot为异步任务规划自定义线程池如何实现
    本篇内容主要讲解“springboot为异步任务规划自定义线程池如何实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“springboot为异步任务规划自定义线程池如何实现”吧!一、Spring...
    99+
    2023-07-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作