基于SpringBoot多线程@Async的使用体验

目录
  • 多线程@Async的使用体验
    • 场景
    • 1.线程池配置
    • 2.子父线程之间共享一个Request的配置方案
    • 3.阻塞主线程,等待所有子线程执行完毕后继续执行主线程
      • 1.CountDownLatch
      • 2.Future
    • 4.多线程共用一个事务
  • 异步调用@Async问题
    • 1.使用背景
    • 2.异步处理方式
    • 3.@Async不返回数据
    • 4.@Async返回数据
    • 5.异常处理

多线程@Async的使用体验

场景

导入:可以将大批量的数据insert操作采用多线程的方式并行执行

第三方服务的接口调用:由于存在个别第三方服务调用比较耗时的场景,此时就可以与自身服务的逻辑并行执行

简而言之:接口中部份业务逻辑可以通过并行的方式来优化接口性能

1.线程池配置

@Configuration
@EnableAsync
public class TaskPoolConfig {

    @Bean("taskExecutor") // bean 的名称,默认为首字母小写的方法名
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数(CPU核心数+1)
        executor.setCorePoolSize(10);
        //最大线程数(2*CPU核心数+1)
        executor.setMaxPoolSize(20);
        //缓冲队列数
        executor.setQueueCapacity(200);
        //允许线程空闲时间(单位:默认为秒)
        executor.setKeepAliveSeconds(60);
        //线程池名前缀
        executor.setThreadNamePrefix("sub-thread-");
        // 增加 TaskDecorator 属性的配置
        executor.setTaskDecorator(new ContextDecorator());
        // 线程池对拒绝任务的处理策略:CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

2.子父线程之间共享一个Request的配置方案

1.实现TaskDecorator接口

/**
 * 子线程装饰器
 *
 * @author Da Shuai
 * @date 2021-06-10 18:28:17
 */
public class SubThreadTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(context);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

2.之前的线程池配置加如下代码使其生效

// 增加 TaskDecorator 属性的配置
executor.setTaskDecorator(new ContextDecorator());

3.阻塞主线程,等待所有子线程执行完毕后继续执行主线程

1.CountDownLatch

思路:

  • 实例化CountDownLatch对象,同时传入x(线程数量:这个数量必须等于子线程数量)进行构造
  • 每个子线程执行完毕后会调用countDown()方法
  • 子线程逻辑后方调用await()方法

这样线程计数器为0之前,主线程就一直处于pending状态

主线程逻辑

new CountDownLatch(X)
latch.await()
@Override
@Transactional
public void importExcel(File file) {
    CountDownLatch latch = new CountDownLatch(3);
    for (int i = 0; i < 3; i++) {
        VoteDO voteDO = new VoteDO();
        voteDO.setTitle(i + "");
        asyncManager.asyncSaveVote(voteDO);
    }
    //System.out.println(1/0);
    try {
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

子线程逻辑

latch.countDown()
@Override
@Async
public void asyncSaveVote(VoteDO voteDO, CountDownLatch latch) {
    log.info("当前线程为 {},休眠10s开始", Thread.currentThread().getName());
    try {
        Thread.sleep(10000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("当前线程为 {},休眠10s结束", Thread.currentThread().getName());
    log.info("当前线程为 {},保存开始", Thread.currentThread().getName());
    voteDO.setDesc(Thread.currentThread().getName());
    voteDao.insert(voteDO);
    latch.countDown();
    log.info("当前线程为 {},保存结束", Thread.currentThread().getName());
}

日志

2021-06-11 16:31:08.653 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : ===============请求内容===============
2021-06-11 16:31:08.653 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : 请求地址:http://localhost:8080/api/import
2021-06-11 16:31:08.653 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : 请求方式:POST
2021-06-11 16:31:08.655 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : 请求类方法:com.zhdj.controller.ImportController.importExcel
2021-06-11 16:31:08.655 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : 请求类方法参数:[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@42c3f403]
2021-06-11 16:31:08.655 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : ===============请求内容===============
2021-06-11 16:31:08.676 INFO 27516 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2021-06-11 16:31:08.894 INFO 27516 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2021-06-11 16:31:08.921 INFO 27516 --- [ sub-thread-3] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-3,休眠10s开始
2021-06-11 16:31:08.921 INFO 27516 --- [ sub-thread-1] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-1,休眠10s开始
2021-06-11 16:31:08.921 INFO 27516 --- [ sub-thread-2] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-2,休眠10s开始
2021-06-11 16:31:18.921 INFO 27516 --- [ sub-thread-2] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-2,休眠10s结束
2021-06-11 16:31:18.921 INFO 27516 --- [ sub-thread-3] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-3,休眠10s结束
2021-06-11 16:31:18.921 INFO 27516 --- [ sub-thread-2] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-2,保存开始
2021-06-11 16:31:18.921 INFO 27516 --- [ sub-thread-1] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-1,休眠10s结束
2021-06-11 16:31:18.921 INFO 27516 --- [ sub-thread-3] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-3,保存开始
2021-06-11 16:31:18.921 INFO 27516 --- [ sub-thread-1] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-1,保存开始
2021-06-11 16:31:19.080 DEBUG 27516 --- [ sub-thread-3] com.zhdj.dao.VoteDao.insert : ==> Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:31:19.080 DEBUG 27516 --- [ sub-thread-1] com.zhdj.dao.VoteDao.insert : ==> Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:31:19.080 DEBUG 27516 --- [ sub-thread-2] com.zhdj.dao.VoteDao.insert : ==> Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:31:19.156 DEBUG 27516 --- [ sub-thread-1] com.zhdj.dao.VoteDao.insert : ==> Parameters: 0(String), sub-thread-1(String), 2021-06-11T16:31:19.032(LocalDateTime), 2021-06-11T16:31:19.037(LocalDateTime)
2021-06-11 16:31:19.156 DEBUG 27516 --- [ sub-thread-3] com.zhdj.dao.VoteDao.insert : ==> Parameters: 2(String), sub-thread-3(String), 2021-06-11T16:31:19.032(LocalDateTime), 2021-06-11T16:31:19.037(LocalDateTime)
2021-06-11 16:31:19.156 DEBUG 27516 --- [ sub-thread-2] com.zhdj.dao.VoteDao.insert : ==> Parameters: 1(String), sub-thread-2(String), 2021-06-11T16:31:19.032(LocalDateTime), 2021-06-11T16:31:19.037(LocalDateTime)
2021-06-11 16:31:19.172 DEBUG 27516 --- [ sub-thread-3] com.zhdj.dao.VoteDao.insert : <== Updates: 1
2021-06-11 16:31:19.178 DEBUG 27516 --- [ sub-thread-2] com.zhdj.dao.VoteDao.insert : <== Updates: 1
2021-06-11 16:31:19.187 DEBUG 27516 --- [ sub-thread-1] com.zhdj.dao.VoteDao.insert : <== Updates: 1
2021-06-11 16:31:19.224 INFO 27516 --- [ sub-thread-3] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-3,保存结束
2021-06-11 16:31:19.224 INFO 27516 --- [ sub-thread-1] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-1,保存结束
2021-06-11 16:31:19.224 INFO 27516 --- [ sub-thread-2] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-2,保存结束
2021-06-11 16:31:19.226 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : --------------返回内容----------------
2021-06-11 16:31:19.328 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : Response内容:null
2021-06-11 16:31:19.328 INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect : --------------返回内容----------------

2.Future

思路:

1.子线程逻辑返回Future对象

2.主线程逻辑循环判断每个子线程返回的Future对象isDone()是否为true

主线程逻辑

循环判断future.isDone()是否为true

@Override
@Transactional
public void importExcel(File file) {
 List<Future> futureList = new ArrayList<>();
 for (int i = 0; i < 3; i++) {
     VoteDO voteDO = new VoteDO();
     voteDO.setTitle(i + "");
     Future future = asyncManager.asyncSaveVote(voteDO);
     futureList.add(future);
 }
 //检查所有子线程是否均执行完毕
 while (true) {
     boolean isAllDone = true;
     for (Future future : futureList) {
         if (null == future || !future.isDone()) {
             isAllDone = false;
         }
     }
     if (isAllDone) {
         log.info("所有子线程执行完毕");
         break;
     }
 }
}

子线程逻辑

返回Future对象

@Override
public Future asyncSaveVote(VoteDO voteDO) {
    log.info("当前线程为 {},休眠10s开始", Thread.currentThread().getName());
    try {
        Thread.sleep(10000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("当前线程为 {},休眠10s结束", Thread.currentThread().getName());
    log.info("当前线程为 {},保存开始", Thread.currentThread().getName());
    voteDO.setDesc(Thread.currentThread().getName());
    voteDao.insert(voteDO);
    log.info("当前线程为 {},保存结束", Thread.currentThread().getName());
    //返回需要用AsyncResult类
    return new AsyncResult<>(true);
}

日志

2021-06-11 16:42:28.974 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : ===============请求内容===============
2021-06-11 16:42:28.974 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : 请求地址:http://localhost:8080/api/import
2021-06-11 16:42:28.974 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : 请求方式:POST
2021-06-11 16:42:28.975 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : 请求类方法:com.zhdj.controller.ImportController.importExcel
2021-06-11 16:42:28.975 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : 请求类方法参数:[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@7e23bacc]
2021-06-11 16:42:28.975 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : ===============请求内容===============
2021-06-11 16:42:28.979 INFO 20492 --- [ sub-thread-5] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-5,休眠10s开始
2021-06-11 16:42:28.979 INFO 20492 --- [ sub-thread-4] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-4,休眠10s开始
2021-06-11 16:42:28.979 INFO 20492 --- [ sub-thread-6] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-6,休眠10s开始
2021-06-11 16:42:38.980 INFO 20492 --- [ sub-thread-6] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-6,休眠10s结束
2021-06-11 16:42:38.980 INFO 20492 --- [ sub-thread-4] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-4,休眠10s结束
2021-06-11 16:42:38.980 INFO 20492 --- [ sub-thread-5] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-5,休眠10s结束
2021-06-11 16:42:38.980 INFO 20492 --- [ sub-thread-6] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-6,保存开始
2021-06-11 16:42:38.980 INFO 20492 --- [ sub-thread-5] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-5,保存开始
2021-06-11 16:42:38.980 INFO 20492 --- [ sub-thread-4] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-4,保存开始
2021-06-11 16:42:38.981 DEBUG 20492 --- [ sub-thread-4] com.zhdj.dao.VoteDao.insert : ==> Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:42:38.981 DEBUG 20492 --- [ sub-thread-5] com.zhdj.dao.VoteDao.insert : ==> Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:42:38.981 DEBUG 20492 --- [ sub-thread-6] com.zhdj.dao.VoteDao.insert : ==> Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:42:38.982 DEBUG 20492 --- [ sub-thread-5] com.zhdj.dao.VoteDao.insert : ==> Parameters: 1(String), sub-thread-5(String), 2021-06-11T16:42:38.980(LocalDateTime), 2021-06-11T16:42:38.981(LocalDateTime)
2021-06-11 16:42:38.982 DEBUG 20492 --- [ sub-thread-4] com.zhdj.dao.VoteDao.insert : ==> Parameters: 0(String), sub-thread-4(String), 2021-06-11T16:42:38.980(LocalDateTime), 2021-06-11T16:42:38.981(LocalDateTime)
2021-06-11 16:42:38.982 DEBUG 20492 --- [ sub-thread-6] com.zhdj.dao.VoteDao.insert : ==> Parameters: 2(String), sub-thread-6(String), 2021-06-11T16:42:38.980(LocalDateTime), 2021-06-11T16:42:38.981(LocalDateTime)
2021-06-11 16:42:38.988 DEBUG 20492 --- [ sub-thread-5] com.zhdj.dao.VoteDao.insert : <== Updates: 1
2021-06-11 16:42:38.989 INFO 20492 --- [ sub-thread-5] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-5,保存结束
2021-06-11 16:42:38.993 DEBUG 20492 --- [ sub-thread-6] com.zhdj.dao.VoteDao.insert : <== Updates: 1
2021-06-11 16:42:38.993 INFO 20492 --- [ sub-thread-6] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-6,保存结束
2021-06-11 16:42:39.004 DEBUG 20492 --- [ sub-thread-4] com.zhdj.dao.VoteDao.insert : <== Updates: 1
2021-06-11 16:42:39.005 INFO 20492 --- [ sub-thread-4] com.zhdj.AsyncManagerImpl : 当前线程为 sub-thread-4,保存结束
2021-06-11 16:42:39.005 INFO 20492 --- [nio-8080-exec-2] com.zhdj.service.impl.VoteServiceImpl : 所有子线程执行完毕
2021-06-11 16:42:39.005 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : --------------返回内容----------------
2021-06-11 16:42:39.005 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : Response内容:null
2021-06-11 16:42:39.005 INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect : --------------返回内容----------------

4.多线程共用一个事务

暂时无解决方案,这是弊端

异步调用@Async问题

1.使用背景

在项目中,当访问其他人的接口较慢或者做耗时任务时,不想程序一直卡在耗时任务上,想程序能够并行执行,我们可以使用多线程来并行的处理任务,也可以使用spring提供的异步处理方式@Async。

2.异步处理方式

调用之后,不返回任何数据。

调用之后,返回数据,通过Future来获取返回数据

3.@Async不返回数据

使用@EnableAsync启用异步注解

@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig{
}

在异步处理的方法dealNoReturnTask上添加注解@Async

@Component
@Slf4j
public class AsyncTask {

    @Async
    public void dealNoReturnTask(){
        log.info("Thread {} deal No Return Task start", Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Thread {} deal No Return Task end at {}", Thread.currentThread().getName(), System.currentTimeMillis());
    }
}

Test测试类:

@SpringBootTest(classes = SpringbootApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
@Slf4j
public class AsyncTest {
    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void testDealNoReturnTask(){
        asyncTask.dealNoReturnTask();
        try {
            log.info("begin to deal other Task!");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

日志打印结果为:

begin to deal other Task!
AsyncExecutorThread-1 deal No Return Task start
AsyncExecutorThread-1 deal No Return Task end at 1499751227034

从日志中我们可以看出,方法dealNoReturnTask()是异步执行完成的。

dealNoReturnTask()设置sleep 3s是为了模拟耗时任务

testDealNoReturnTask()设置sleep 10s是为了确认异步是否执行完成

4.@Async返回数据

异步调用返回数据,Future表示在未来某个点获取执行结果,返回数据类型可以自定义

    @Async
    public Future<String> dealHaveReturnTask() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("thread", Thread.currentThread().getName());
        jsonObject.put("time", System.currentTimeMillis());
        return new AsyncResult<String>(jsonObject.toJSONString());
    }

测试类用isCancelled判断异步任务是否取消,isDone判断任务是否执行结束

    @Test
    public void testDealHaveReturnTask() throws Exception {

        Future<String> future = asyncTask.dealHaveReturnTask();
        log.info("begin to deal other Task!");
        while (true) {
            if(future.isCancelled()){
                log.info("deal async task is Cancelled");
                break;
            }
            if (future.isDone() ) {
                log.info("deal async task is Done");
                log.info("return result is " + future.get());
                break;
            }
            log.info("wait async task to end ...");
            Thread.sleep(1000);
        }
    }

日志打印如下,我们可以看出任务一直在等待异步任务执行完毕,用future.get()来获取异步任务的返回结果

begin to deal other Task!
wait async task to end ...
wait async task to end ...
wait async task to end ...
wait async task to end ...
deal async task is Done
return result is {"thread":"AsyncExecutorThread-1","time":1499752617330}

5.异常处理

我们可以实现AsyncConfigurer接口,也可以继承AsyncConfigurerSupport类来实现

在方法getAsyncExecutor()中创建线程池的时候,必须使用 executor.initialize(),

不然在调用时会报线程池未初始化的异常。

如果使用threadPoolTaskExecutor()来定义bean,则不需要初始化

@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig implements AsyncConfigurer {

//    @Bean
//    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
//        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//        executor.setCorePoolSize(10);
//        executor.setMaxPoolSize(100);
//        executor.setQueueCapacity(100);
//        return executor;
//    }

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("AsyncExecutorThread-");
        executor.initialize(); //如果不初始化,导致找到不到执行器
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }
}

异步异常处理类:

@Slf4j
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        log.info("Async method: {} has uncaught exception,params:{}", method.getName(), JSON.toJSONString(params));

        if (ex instanceof AsyncException) {
            AsyncException asyncException = (AsyncException) ex;
            log.info("asyncException:{}",asyncException.getErrorMessage());
        }

        log.info("Exception :");
        ex.printStackTrace();
    }
}

异步处理异常类:

@Data
@AllArgsConstructor
public class AsyncException extends Exception {
    private int code;
    private String errorMessage;
}

在无返回值的异步调用中,异步处理抛出异常,AsyncExceptionHandler的handleUncaughtException()会捕获指定异常,原有任务还会继续运行,直到结束。

在有返回值的异步调用中,异步处理抛出异常,会直接抛出异常,异步任务结束,原有处理结束执行。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot异步使用@Async的原理以及线程池配置详解

    目录 前言 使用步骤 配置线程池类参数配置 自定义线程任务 总结 原理刨析 文章参考 前言 在实际项目开发中很多业务场景需要使用异步去完成,比如消息通知,日志记录,等非常常用的都可以通过异步去执行,提高效率,那么在Spring框架中应该如何去使用异步呢 使用步骤 完成异步操作一般有两种,消息队列MQ,和线程池处理ThreadPoolExecutor 而在Spring4中提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor,直接使用注解启用@Asyn

  • springboot实现异步调用@Async的示例

    在后端开发中经常遇到一些耗时或者第三方系统调用的情况,我们知道Java程序一般的执行流程是顺序执行(不考虑多线程并发的情况),但是顺序执行的效率肯定是无法达到我们的预期的,这时就期望可以并行执行,常规的做法是使用多线程或线程池,需要额外编写代码实现.在spring3.0后引入了@Async注解,使用该注解可以达到线程池的执行效果,而且在开发上非常简单. 一.概述 springboot是基于spring框架的,在springboot环境下演示@Async注解的使用方式.先看下该注解的定义, @Ta

  • SpringBoot用@Async注解实现异步任务

    什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行. 如何实现异步调用? 多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式. 在非spring目项目中我们要实现异步调用的就是使用多线程方式,可以自己实现Runable接口或者集成Thread类,或者使用jdk1.5以上提供了的Executors线程池. StrngBoot中则提供了很方便的方式执行异步调

  • springboot @Async 注解如何实现方法异步

    目录 @Async注解如何实现方法异步 一.springboot的App类需要的注解 二.service层的注解 三.调用层 异步注解@Async的使用以及注意事项 第一步开启异步 下面显示配置线程的代码实现 使用@Async导致异步不成功的情况 @Async注解如何实现方法异步 处理大批量数据的时候,效率很慢.所以考虑一下使用多线程. 刚开始自己手写的一套,用了线程池启动固定的线程数进行跑批.但是后来老大考虑到自己手写的风险不好控制,所以使用spring的方法. 这里没有详细介绍,只有简单的d

  • 详解springboot使用异步注解@Async获取执行结果的坑

    目录 一.引言 二.获取异步执行结果 1.环境介绍 2.错误的方式 3.正确方式 三.异步执行@Async注解 四.总结 一.引言 在java后端开发中经常会碰到处理多个任务的情况,比如一个方法中要调用多个请求,然后把多个请求的结果合并后统一返回,一般情况下调用其他的请求一般都是同步的,也就是每个请求都是阻塞的,那么这个处理时间必定是很长的,有没有一种方法可以让多个请求异步处理那,答案是有的. springboot中提供了很便利的方式可以解决上面的问题,那就是异步注解@Async.正确的使用该注

  • Springboot 如何使用@Async整合线程池

    Springboot @Async整合线程池 开篇咱们先来聊聊线程池这个概念,或者说为什么要使用线程池:简言之,充分利用cpu资源,提高程序执行时间,但是相反,线程池异常提示.主线程和子线程事务问题也是显而易见的. 那么@Async这个注解又是什么做用呢?其实就是标识方法为异步任务的一个注解,默认会自己维护一个线程池(存在弊端),利用子线程去执行任务:那么如果把这两者结合的话,线程池+Async又会有什么效果呢! 循序渐进 提到线程池,可以采用Executors提供四种线程池下,使用某些特性的场

  • 关于SpringBoot使用@Async的总结

    目录 SpringBoot使用@Async总结 如下方式会使@Async失效 SpringBoot实现异步(Async)接口 1. 启动类引入@EnableAsync注解 2. 建立异步任务类 3. 建立测试接口 SpringBoot使用@Async总结 注意事项: 如下方式会使@Async失效 1. 异步方法使用static修饰 2. 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类 3. 异步方法不能与异步方法在同一个类中 4. 类中需要使用@Autowi

  • 基于SpringBoot多线程@Async的使用体验

    目录 多线程@Async的使用体验 场景 1.线程池配置 2.子父线程之间共享一个Request的配置方案 3.阻塞主线程,等待所有子线程执行完毕后继续执行主线程 1.CountDownLatch 2.Future 4.多线程共用一个事务 异步调用@Async问题 1.使用背景 2.异步处理方式 3.@Async不返回数据 4.@Async返回数据 5.异常处理 多线程@Async的使用体验 场景 导入:可以将大批量的数据insert操作采用多线程的方式并行执行 第三方服务的接口调用:由于存在个

  • 基于Springboot实现JWT认证的示例代码

    目录 一.了解JWT 概念 作用 1.1 为什么授权要使用jwt 二.JWT结构 2.1 header 2.2 payload 2.3 signature 三.使用JWT 3.1 上手 3.2 封装工具类 3.3 整合springboot 最近一直想写一个类似于待办的东西,由于不想用传统的session,就卡住了,后来在各种群里扯皮,发现除了用缓存之外,还可以通过 JWT 来实现. 一.了解JWT 概念 json web token 用于在各方之间以 json 对象安全地传输信息,比如在前端和后

  • 解决springboot 多线程使用MultipartFile读取excel文件内容报错问题

    springboot项目开启多线程 启动类加注解开启 @EnableAsync,实现类方法加注解 @Async 前端页面 报错信息 java.io.FileNotFoundException: C:\Users\dongao\AppData\Local\Temp\tomcat.1255209411477782290.8051\work\Tomcat\localhost\ROOT\upload_7d7b99e5_38da_4a03_93e0_bff20cb48022_00000000.tmp (系

  • java编程之基于SpringBoot框架实现扫码登录

    目录 项目简介 实现思路 二次认证的原因 实现步骤 用户访问网页端,选择扫码登录 使用手机扫码,二维码状态改变 手机确认登录 效果演示 完整代码已上传到GitHub. Web端体验地址:http://47.116.72.33/(只剩一个月有效期) apk下载地址:https://github.com/zhangjiwei1221/qrscan/releases/tag/0.0.1. 用户名:非空即可,密码:123456,效果见文末,整体实现如有不妥之处,欢迎交流讨论 实现部分参考二维码扫码登录是

  • SpringBoot多线程进行异步请求的处理方式

    目录 SpringBoot多线程进行异步请求的处理 第一步:编写配置类 第二步:对方法使用注解标注为使用多线程进行处理 SpringBoot请求线程优化 使用Callable来实现 1.异步调用的另一种方式 3.Deferred方式实现异步调用 SpringBoot多线程进行异步请求的处理 近期在协会博客园中,有人发布了博客,系统进行查重的时候由于机器最低配置进行大量计算时需要十秒左右时间才能处理完,由于一开始是单例模式,导致,在某人查重的时候整个系统是不会再响应别的请求的,导致了系统假死状态,

  • java SpringBoot注解@Async不生效的解决方法

    目录 问题描述: 解决方案: 总结: SpringBoot 注解@Async不生效的解决方法 问题描述: 这里虽然加了@EnableAsync和@Async,但是异步请求依然没有生效 解决方案: 方法一: 同一个类中调用需要先获取代理对象,也就是手动获取对象 @Service @EnableAsync public class DemoService { public void add(){ DemoService bean = SpringUtil.getBean(DemoService.cl

  • 基于SpringBoot上传任意文件功能的实现

    一.pom文件依赖的添加 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</gr

  • 基于SpringBoot与Mybatis实现SpringMVC Web项目

    一.热身 一个现实的场景是:当我们开发一个Web工程时,架构师和开发工程师可能更关心项目技术结构上的设计.而几乎所有结构良好的软件(项目)都使用了分层设计.分层设计是将项目按技术职能分为几个内聚的部分,从而将技术或接口的实现细节隐藏起来. 从另一个角度上来看,结构上的分层往往也能促进了技术人员的分工,可以使开发人员更专注于某一层业务与功能的实现,比如前端工程师只关心页面的展示与交互效果(例如专注于HTML,JS等),而后端工程师只关心数据和业务逻辑的处理(专注于Java,Mysql等).两者之间

  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,docker的镜像也原来越丰富,未来各种企业级的完整解决方案都可以直接通过下载镜像拿来即用.因此docker变得越来越重要. 本文目的 本文通过一个项目实例来介绍如果通过docker对外接口来实现对docker容器的管理和监控. 应用场景: 对服务器资源池通过docker进行统一管理,按需分配资源和创建容器,达到资源最大化利

随机推荐