一、基础知识 同步:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去; 异步:异步是指进程不需要一直等下去,而是继续执行下面的操作。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。 进程:进程是独立的应用程序,占用cpu资源和物理内存。一个进程包括由操作系统分配的内存空间,包含一个或多个线程; 线程:线程是进程中虚拟的时间片,一个线程不能独立的存在,它必须是进程的一部分。 多线程:实际上就是时间片的轮转或者抢占。多线程能满足编写高效率的程序来达到充分利用CPU的目的;二、什么时候用同步异步 什么时候用同步:如果数据在线程间共享,例如正在写的数据可能被另外一个线程读到,而正在读的数据可能被另外一个线程写到,这些数据是共享的数据。这时就必须进行同步存取操作,否者前后读取的数据就有可能不一致; 什么时候用异步:调用一个需要花费很长时间来执行的方法的时候,并且不需要让程序等待对方返回,这时就应该使用异步编程; 必须使用同步的场景举例: 有一个共享的银行账号,原来里面有余额1000元,现在有两个用户A,B都要进行取钱; 首先A查询账号剩余1000元,A想要取出200元,A点击取款,系统正在处理取款事项 紧接着在A取款的过程中B查询同一个账号还有1000元,B也想要取走200元; A取完款后剩余800元,正常。而B取完款后理论上应该剩余600元,但是实际上还是剩余800元。这种场景就必须使用同步,而不能使用异步;三、什么时候需要使用多线程 举个例子: 假设有个请求,服务端的处理需要执行3个比较耗时的操作: 1、操作1(200ms) 2、操作2(200ms) 3、操作3(200ms) 单线程总共就需要600ms,但如果把操作1、操作2、操作3分别分给3个线程去做,就只需要200ms了。 但是假设另外一个请求,服务端的处理也需要执行3个操作: 1、操作1(10ms) 2、操作2(10ms) 3、操作3(400ms) 单线程总共就需要420ms,这种情况下,即使把操作1、操作2、操作3分别分给3个线程去做,也需要400ms(耗时取决于最慢的那个线程的执行速度)。比起不用单线程,只节省了20ms。但是有可能线程调度切换也要花费个1、2ms。因此,这个方案显得优势就不明显了,还带来程序复杂度提升,不太值得,此时更好的方案是去优化降低操作3的耗时。四、Springboot异步多线程编程实现 4。1使用idea创建springbootweb项目,工程最终目录结构如下: 4。2首先创建springboot的线程池配置: common包下面创建ExecutorConfig类,用于自定义线程池的相关配置。使用Configuration和EnableAsync这两个注解,表示这是线程池的配置类。ConfigurationEnableAsyncSlf4jpublicclassExecutorConfig{核心线程数(默认线程数)privateintcorePoolSize10;最大线程数privateintmaxPoolSize20;允许线程空闲时间(单位:默认为秒)privatestaticfinalintkeepAliveTime60;缓冲队列大小privateintqueueCapacity10;BeanpublicExecutorasyncServiceExecutor(){log。info(startasyncServiceExecutor);ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();配置核心线程数executor。setCorePoolSize(corePoolSize);配置最大线程数executor。setMaxPoolSize(maxPoolSize);配置空闲时间executor。setKeepAliveSeconds(keepAliveTime);配置队列大小executor。setQueueCapacity(queueCapacity);配置线程前缀名executor。setThreadNamePrefix(asyncservice);rejectionpolicy:当pool已经达到maxsize的时候,如何处理新任务CALLERRUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor。setRejectedExecutionHandler(newThreadPoolExecutor。CallerRunsPolicy());执行初始化executor。initialize();}} 4。3service层接口和实现: service包下新增server层的接口AsyncService类和对应的实现类AsyncServiceImpl。AsyncService内容如下:publicinterfaceAsyncService{执行异步任务voidexecuteAsync();} AsyncServiceImpl类内容如下,注意: 1。在executeAsync方法上增加注解Async(asyncServiceExecutor); 2。Async表示使用异步实现方式 3。括号里的asyncServiceExecutor是前面ExecutorConfig。java中的方法名,表明executeAsync方法使用asyncServiceExecutor方法创建的线程池多线程执行:Slf4jServicepublicclassAsyncServiceImplimplementsAsyncService{异步多线程调用Async(asyncServiceExecutor)publicvoidexecuteAsync(){log。info(startexecuteAsync);try{Thread。sleep(2000);}catch(Exceptione){e。printStackTrace();}log。info(endexecuteAsync);}} 4。4controller层实现: 创建HelloController类,新增testhttp接口,调用server层的executeAsync服务。Slf4jRestControllerpublicclassHelloController{AutowiredprivateAsyncServiceasyncS异步多线程调用方法,不用等方法返回结果RequestMapping(test)publicStringtest(){log。info(startsubmit);调用service层的任务asyncService。executeAsync();log。info(endsubmit);}} 4。5验证效果: 验证异步效果: 1。先将ExecutorConfig类下corePoolSize设置为1,表示只用1个线程。然后运行 2。springboot启动成功后,在浏览器输入:http:localhost:8080test。可以看到虽然我们前面AsyncServiceImpl代码中sleep了2秒,但由于使用的是异步实现,所以接口马上直接先返回了success,而不需要等待2秒后再返回。 后台日志也能看到,异步接口controller层很快就执行结束,然后service方法继续按代码执行了2秒: 验证多线程效果: 1。corePoolSize设置为1时,使用Jmeter同时调用接口:http:localhost:8080test4次; 2。在springboot的控制台看见日志如下: 可以看出是1个线程每隔2秒执行完一次startendexecuteAsync,执行4次总共花费了8秒时间; 3。将corePoolSize设置为10,重启 4。再次使用Jmeter同时调用接口:http:localhost:8080test4次; 5。在springboot的控制台看见日志如下: 可以看出是4个线程同时在执行,执行完4次startendexecuteAsync,总共花费了2秒时间,这就是多线程可以提高程序运行效率的体现。 4。6获取多线程的返回值: Java自jdk1。5以后,提供了java。util。concurrent。Future来获取异步线程返回的结果。主线程会创建一个Future接口的对象,然后启动并发线程,并告诉并发线程,一旦你执行完毕,就把结果存储在这个Future对象里。 一般情况下,我们会把长时间运行的逻辑放在异步线程中进行处理,这是使用Future接口最理想的场景。主线程只要简单的将异步任务封装在Future里,然后开始等待Future的完成,在这段等待的时间内,可以处理一些其它逻辑,一旦Future执行完毕,就可以从中获取执行的结果并进一步处理。 AsyncServiceImpl类中增加两个方法:多线程调用并获取回调结果Async(asyncServiceExecutor)publicFutureStringsendMessageAsync1(){log。info(异步发送消息1执行开始);try{Thread。sleep(2000);}catch(InterruptedExceptione){e。printStackTrace();}log。info(异步发送消息1执行结束);returnnewAsyncResult(异步发送消息1);}Async(asyncServiceExecutor)publicFutureStringsendMessageAsync2(){log。info(异步发送消息2执行开始);try{Thread。sleep(2000);}catch(InterruptedExceptione){e。printStackTrace();}log。info(异步发送消息2执行结束);returnnewAsyncResult(异步发送消息2);} AsyncService类中增加接口:FutureStringsendMessageAsync1();FutureStringsendMessageAsync2(); Controller中增加http接口调用:异步多线程调用,但是要等方法回调结果。用多线程,所以只需要2秒RequestMapping(sendMessageAsync)publicStringsendMessageAsync()throwsExecutionException,InterruptedException{System。out。println(开始时间:newDate());FutureStringsendMessageAsync1asyncService。sendMessageAsync1();FutureStringsendMessageAsync2asyncService。sendMessageAsync2();SStringresult1;Stringresult2;while(!(sendMessageAsync1。isDone()sendMessageAsync2。isDone())){System。out。println(String。format(future1issandfuture2iss,sendMessageAsync1。isDone()?done:notdone,sendMessageAsync2。isDone()?done:notdone));Thread。sleep(300);}resultsendMessageAsync1。get();resultsendMessageAsync2。get();System。out。println(结束时间:newDate()); 上面使用的是先调用Future。isDone()判断任务是否完成,再调用Future。get()从完成的任务中获取任务执行的结果。 也可以直接用Future。get()并设置一个超时时间:RequestMapping(sendMessageAsync)publicStringsendMessageAsync()throwsExecutionException,InterruptedException{System。out。println(开始时间:newDate());FutureStringsendMessageAsync1asyncService。sendMessageAsync1();FutureStringsendMessageAsync2asyncService。sendMessageAsync2();SStringresult1;Stringresult2;通过future。get()方法阻塞性获取执行结果,设置超时时间为3秒,3秒还没获取到值,就超时报错try{result1sendMessageAsync1。get(3000,TimeUnit。MILLISECONDS);}catch(TimeoutExceptione){sendMessageAsync1。cancel(true);log。error(sendMessageAsync1方法超时未返回结果);e。printStackTrace();}try{result2sendMessageAsync2。get(3000,TimeUnit。MILLISECONDS);}catch(TimeoutExceptione){sendMessageAsync2。cancel(true);log。error(sendMessageAsync2方法超时未返回结果);e。printStackTrace();}resultresult1result2;System。out。println(结束时间:newDate());} Future。get()方法是一个阻塞方法。如果任务还没执行完毕,那么会一直阻塞直到直到任务完成 为了防止调用Future。get()方法阻塞当前线程,推荐的做法是先调用Future。isDone()判断任务是否完成,然后再调用Future。get()从完成的任务中获取任务执行的结果。 因为Future。isDone()和Future。get()的存在,我们就可以在等待任务完成时运行其它一些代码。使用isDone()和get()方法来获取结果,这应该是消费Future最常见的方式。 针对上面的代码,如果不用isDone(),直接用get(),那么get()阻塞的这2秒内就不能做任何其他事情。而用了whileisDone(),这2秒内则可以做一些其他的事情,比如上面代码中的输出打印一段话。 运行结果如下: 虽然sendMessageAsync1和sendMessageAsync2都要2秒时间,由于是多线程并行处理,所以总共只花费了2秒。 正常的单线程处理:正常的单线程处理,要花4秒RequestMapping(sendmessage)publicStringsendMessage()throwsExecutionException,InterruptedException{System。out。println(newDate());调用service层的任务StringsendMessage1asyncService。sendMessage1();StringsendMessage2asyncService。sendMessage2();SresultsendMessage1sendMessage2;System。out。println(newDate());} 单线程顺序执行sendMessage1和sendMessage2,每一个方法执行需要2秒,总共就需要4秒才能执行完。 以上就是本篇文章的全部内容,如果对你有帮助, 欢迎搜索关注我的微信公众号【程序员杨叔】:测开一枚,持续分享全栈测试知识干货。标签:自动化测试、性能测试、Java、Python、DevOps、CICD、小程序测试、测试工具、测试开发、测试框架平台、测试管理