專業(yè)網(wǎng)站建設(shè)費(fèi)用怎么算/華為手機(jī)軟文范文300
背景
在需求開發(fā)過(guò)程中,為了提升效率,很容易就會(huì)遇到需要使用多線程的場(chǎng)景。這個(gè)時(shí)候一般都會(huì)選擇建一個(gè)線程池去專門用來(lái)進(jìn)行某一類動(dòng)作,這種任務(wù)到來(lái)的時(shí)候往往伴隨著大量的線程被創(chuàng)建調(diào)用。而還有另外一種場(chǎng)景是整個(gè)任務(wù)的執(zhí)行耗時(shí)比較長(zhǎng),但又不適合起多線程去運(yùn)行,只能后臺(tái)起一個(gè)異步線程去慢慢跑。這個(gè)時(shí)候就需要一個(gè)公共的線程池。
可選方案
總體思想就是要有一個(gè)全局可用的線程池,可以用來(lái)執(zhí)行一些零散的任務(wù)。
方案一
自定義一個(gè)全局的線程池,需要異步操作的就調(diào)用。這種方法好處是實(shí)現(xiàn)簡(jiǎn)單,并且調(diào)用起來(lái)也簡(jiǎn)單,直接當(dāng)成一個(gè)方法就可以了。但需要同模塊項(xiàng)目(或者導(dǎo)入了模塊)才能使用。
方案二
使用Spring自帶的注解@Async實(shí)現(xiàn)異步。這種方法的好處是注解可以跨模塊使用,因?yàn)榫€程池對(duì)象會(huì)被注入容器,整個(gè)服務(wù)共用。而且更大的好處是使用簡(jiǎn)單,使用者只需要給所需異步操作的方法加上@Async(“beanName”)即可。其中beanName是指注入容器的對(duì)象的名稱,也可以不加參數(shù),不加參數(shù)代表使用默認(rèn)線程池。
考慮到便捷性和新手友好性,選擇了方案二。
實(shí)現(xiàn)(注意看我的調(diào)用的結(jié)構(gòu),可以避免循環(huán)依賴)
controller->async->service √
controller->service->async->service ×,這樣會(huì)出現(xiàn)循環(huán)依賴
1.創(chuàng)建線程池配置類
/**
* 必須加上@EnableAsync注解
*/
@EnableAsync
@Configuration
public class TaskPoolConfig {/*** 可以多創(chuàng)建幾個(gè)bean注入容器,根據(jù)bean不同用來(lái)執(zhí)行不同類型的任務(wù)*/@Bean("taskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心線程池大小executor.setCorePoolSize(16);//最大線程數(shù)executor.setMaxPoolSize(20);//配置隊(duì)列容量,默認(rèn)值為Integer.MAX_VALUEexecutor.setQueueCapacity(99999);//活躍時(shí)間executor.setKeepAliveSeconds(60);//線程名字前綴executor.setThreadNamePrefix("asyncServiceExecutor -");//設(shè)置此執(zhí)行程序應(yīng)該在關(guān)閉時(shí)阻止的最大秒數(shù),以便在容器的其余部分繼續(xù)關(guān)閉之前等待剩余的任務(wù)完成他們的執(zhí)行executor.setAwaitTerminationSeconds(60);//等待所有的任務(wù)結(jié)束后再關(guān)閉線程池executor.setWaitForTasksToCompleteOnShutdown(true);return executor;}@Bean("taskExecutor2")public Executor taskExecutor2() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心線程池大小executor.setCorePoolSize(16);//最大線程數(shù)executor.setMaxPoolSize(20);//配置隊(duì)列容量,默認(rèn)值為Integer.MAX_VALUEexecutor.setQueueCapacity(99999);//活躍時(shí)間executor.setKeepAliveSeconds(60);//線程名字前綴executor.setThreadNamePrefix("asyncServiceExecutor -");//設(shè)置此執(zhí)行程序應(yīng)該在關(guān)閉時(shí)阻止的最大秒數(shù),以便在容器的其余部分繼續(xù)關(guān)閉之前等待剩余的任務(wù)完成他們的執(zhí)行executor.setAwaitTerminationSeconds(60);//等待所有的任務(wù)結(jié)束后再關(guān)閉線程池executor.setWaitForTasksToCompleteOnShutdown(true);return executor;}
}
2.使用異步
異步類:
public interface AsyncService {void test();
}
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {@Autowiredprivate TestService testService;//這里的參數(shù)是只bean的名稱,不填則使用默認(rèn)的線程池。如果這個(gè)注解放在類上,代表這個(gè)類里面的全部方法都走異步@Async("taskExecutor")void test(){testService.todo();}@Async("taskExecutor2")void test2(){testService.todo2();}
}
業(yè)務(wù)類:
public interface TestService {void todo();void todo2();
}
@Slf4j
@Service
public class TestServiceImpl implements TestService{void todo(){LocalDateTime dateTime=LocalDateTime.now();log.info("已經(jīng)進(jìn)入異步方法,現(xiàn)在時(shí)間:{},睡三秒",dateTime);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}dateTime=LocalDateTime.now();log.info("三秒后時(shí)間為:{}",dateTime);}void todo2(){}
}
測(cè)試類:
@Slf4j
@RestController
public class TestController {@Autowiredprivate AsyncService asyncService;@RequestMapping("/test")public void test(){asyncService.test();LocalDateTime dateTime=LocalDateTime.now();log.info("這是主線程,現(xiàn)在時(shí)間為:{}",dateTime);}
}
結(jié)果:成功
坑點(diǎn)
1.異步失效
如果一個(gè)類里面有兩個(gè)方法A、B,方法B添加了異步注解,方法A調(diào)用方法B,異步不會(huì)生效。
查了一下,好像是因?yàn)楫惒阶⒔獾膶?shí)現(xiàn)用到了動(dòng)態(tài)代理,而一個(gè)類內(nèi)部方法的調(diào)用不會(huì)走代理,也就沒法實(shí)現(xiàn)異步。
因此,建議把異步都放在一個(gè)專門的異步類里面,這個(gè)類的方法只用來(lái)實(shí)現(xiàn)異步,方法內(nèi)部再去調(diào)用真正的業(yè)務(wù)邏輯方法。
2.循環(huán)依賴
正常來(lái)說(shuō),A類中注入B類對(duì)象,B類中再注入A類對(duì)象。這種情況在代碼中并不會(huì)發(fā)生循環(huán)依賴。而在異步注解中會(huì)出現(xiàn)循環(huán)依賴,因?yàn)楫惒阶⒔獾讓訉?shí)現(xiàn)用的是動(dòng)態(tài)代理。