springboot @Async 线程池使用

一、配置类 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

项目

猜你喜欢

转载自www.cnblogs.com/lyon91/p/10097859.html