• springboot @Async 多线程使用


    测试版本

    1.5 和 2.0.4 完全一样

    一、配置类 AsyncConfig

    import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.AsyncConfigurer;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import com.ly.async.AsyncExceptionHandler;
    import java.util.concurrent.Executor;
    
    @Configuration
    @EnableAsync
    public class AsyncConfig implements AsyncConfigurer {
        
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(100);
            executor.setQueueCapacity(100);
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.setAwaitTerminationSeconds(60 * 10);
            executor.setThreadNamePrefix("AsyncThread-");
            executor.initialize(); //如果不初始化,导致找到不到执行器
            return executor;
        }
    
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new AsyncExceptionHandler();
        }
    }
    View Code

    配置线程池的初始化属性

    二、异步线程异常处理器 

    AsyncExceptionHandler

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler{
         
        private static final Logger log = LoggerFactory.getLogger(AsyncExceptionHandler.class);
            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                log.error("Async method has uncaught exception, params:{}" + Arrays.toString(params));
    
                if (ex instanceof AsyncException) {
                    AsyncException asyncException = (AsyncException) ex;
                    log.error("asyncException:"  + asyncException.getMessage());
                }
    
                log.error("Exception :", ex);
            }
    }
    View Code

    没有返回值的线程,如果跑出异常,会被处理,有返回值的异常信息可以通过 get() 方法获取

     通过Future的get方法捕获异常

    三、异步方法

    @Async
        public void dealNoReturnTask() {
            logger.info("返回值为void的异步调用开始" + Thread.currentThread().getName());
            //int a =1/0;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.info("返回值为void的异步调用结束" + Thread.currentThread().getName());
        }
    
        @Async
        public Future<String> dealHaveReturnTask(int i) {
            //int a =1/0;
            logger.info("asyncInvokeReturnFuture, parementer=" + i);
            Future<String> future;
            try {
                Thread.sleep(1000 * i);
                future = new AsyncResult<String>("success:" + i);
            } catch (InterruptedException e) {
                future = new AsyncResult<String>("error");
            }
            return future;
        }

    分别是有返回值和没有返回值的

    四、测试

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ApplicationTests {
    
        private static final Logger log = LoggerFactory.getLogger(ApplicationTests.class);
       
        @Autowired
        private AsyncTask asyncTask;
    
    @Test
        public void testAsync2() throws InterruptedException, ExecutionException {
            Future<String> result = asyncTask.dealHaveReturnTask(2);
            System.out.println("输出" + result.get());
        }
    View Code

    返回success:2

    五、理解多线程

     @Test
        public void testAsync() throws InterruptedException, ExecutionException {
            System.out.println("线程名称" + Thread.currentThread().getName());
            System.out.println("开始S1");
            asyncTask.sum();
            System.out.println("S2 调用完sum()");
        }
    @Async
        public void sum() {
            System.out.println("进入sum");
            
            int sum = 0;
            for(int i = 0; i < 1000; i++) {
                
                sum += i;
            }
            System.out.println("线程名称" + Thread.currentThread().getName());
            System.out.println(" sum " + sum);
        }

    执行到sum方法的时候,主线程并没有停止,而是继续,子线程去执行里面的方法

     六、使用场景

    1、场景

      最近做项目的时候遇到了一个小问题:从前台提交到服务端A,A调用服务端B处理超时,原因是前端一次请求往db插1万数据,插完之后会去清理缓存、发送消息。

    服务端的有三个操作 a、插DB b、清理cache  c、发送消息。1万条数据,说多不多,说少不少.况且不是单单insert。出现超时现象,不足为奇了吧~~

    2、分析

      如何避免超时呢?一次请求处理辣么多数据,可分多次请求处理,每次请求处理100条数据,可以有效解决超时问题. 为了不影响用户的体验,请求改为ajax 异步请求。

    除此之外,仔细分析下场景. a 操作是本次处理的核心. 而b、c操作可以异步处理。换句话说,a操作处理完可以马上返回给结果, 不必等b、c处理完再返回。b、c操作可以放在一个异步任务去处理。

    -------------

    --举个简单的例子:
    假设有个请求,这个请求服务端的处理需要执行3个很缓慢的IO操作(比如数据库查询或文件查询),那么正常的顺序可能是(括号里面代表执行时间):
    a、读取文件1  (10ms)
    b、处理1的数据(1ms)
    c、读取文件2  (10ms)
    d、处理2的数据(1ms)
    e、读取文件3  (10ms)
    f、处理3的数据(1ms)
    g、整合1、2、3的数据结果 (1ms)
    单线程总共就需要34ms。
    那如果你在这个请求内,把ab、cd、ef分别分给3个线程去做,就只需要12ms了。

    所以多线程不是没怎么用,而是,你平常要善于发现一些可优化的点。然后评估方案是否应该使用。
    假设还是上面那个相同的问题:但是每个步骤的执行时间不一样了。
    a、读取文件1  (1ms)
    b、处理1的数据(1ms)
    c、读取文件2  (1ms)
    d、处理2的数据(1ms)
    e、读取文件3  (28ms)
    f、处理3的数据(1ms)
    g、整合1、2、3的数据结果 (1ms)
    单线程总共就需要34ms。
    如果还是按上面的划分方案(上面方案和木桶原理一样,耗时取决于最慢的那个线程的执行速度),在这个例子中是第三个线程,执行29ms。那么最后这个请求耗时是30ms。比起不用单线程,就节省了4ms。但是有可能线程调度切换也要花费个1、2ms。因此,这个方案显得优势就不明显了,还带来程序复杂度提升。不太值得。

    那么现在优化的点,就不是第一个例子那样的任务分割多线程完成。而是优化文件3的读取速度。
    可能是采用缓存和减少一些重复读取。
    首先,假设有一种情况,所有用户都请求这个请求,那其实相当于所有用户都需要读取文件3。那你想想,100个人进行了这个请求,相当于你花在读取这个文件上的时间就是28×100=2800ms了。那么,如果你把文件缓存起来,那只要第一个用户的请求读取了,第二个用户不需要读取了,从内存取是很快速的,可能1ms都不到。

    参考:

    https://www.cnblogs.com/chenmo-xpw/p/5652029.html

    项目

  • 相关阅读:
    算法学习【第2篇】:列表查找以及二分查找
    算法学习【第1篇】:算法之基础
    九、爬虫框架之Scrapy
    八、asynicio模块以及爬虫应用asynicio模块(高性能爬虫)
    第七篇:爬虫实战— 4、爬取校花网视频示例(点开往下拉)
    第七篇:爬虫实战— 3、自动登录123并且自动发送邮箱;自动爬取京东商品信息
    第七篇:爬虫实战—2、投递拉钩网简历
    第七篇:爬虫实战--- 1、破解滑动验证码
    Ubuntu安装JDK与环境变量配置
    显示 Ubuntu 11.10 的 终端窗口
  • 原文地址:https://www.cnblogs.com/lyon91/p/10097859.html
Copyright © 2020-2023  润新知