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