测试版本
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(); } }
配置线程池的初始化属性
二、异步线程异常处理器
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); } }
没有返回值的线程,如果跑出异常,会被处理,有返回值的异常信息可以通过 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()); }
返回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
项目