聲明:涉及到隱私問(wèn)題,故該系列文章不會(huì)有關(guān)項(xiàng)目詳細(xì)的代碼實(shí)現(xiàn)與項(xiàng)目細(xì)節(jié),僅用作個(gè)人記錄。
一. 背景
承接上篇link
現(xiàn)在項(xiàng)目中有以下線程池(starter中部分):
@Bean(name = "threadPoolExecutor")
@ConditionalOnMissingBean({ThreadPoolExecutor.class})
public ThreadPoolExecutor createThreadPoolExecutor() {
// 創(chuàng)建線程池
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("query-pool-%d")
.daemon(true).build();
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(/*參數(shù)*/);
return threadPoolExecutor;
}
@Bean("chainThreadPoolTaskExecutor")
@ConditionalOnMissingBean({ChainThreadPoolTaskExecutor.class})
public AsyncTaskExecutor taskExecutor() {
log.info("taskExecutor()執(zhí)行");
ThreadPoolConfigProperties configProperties = properties;
ThreadPoolTaskExecutor executor = new ChainThreadPoolTaskExecutor();
executor.setThreadNamePrefix("Script-Executor");
/*設(shè)置其他參數(shù)*/
executor.initialize();
return executor;
}
針對(duì)上面兩個(gè)線程池一知半解,通過(guò)項(xiàng)目中的代碼也沒(méi)有明確出他們使用的具體的場(chǎng)景。現(xiàn)在結(jié)合網(wǎng)上一些資料與代碼測(cè)試跑一下區(qū)別。
二 代碼區(qū)別
仔細(xì)看上面兩個(gè)線程池,ThreadPoolExecutor是juc包下的,AsyncTaskExecutor與ThreadPoolTaskExecutor是spring的。ThreadPoolExecutor的類間關(guān)系如下:

在
ThreadPoolExecutor源代碼中,都是基于ThreadPoolExecutor做的封裝,所以ThreadPoolExecutor是Spring提供的基于ThreadPoolExecutor的封裝的線程池。功能大體相同。這里仍然只研究他們的執(zhí)行任務(wù)上的區(qū)別。
三 使用區(qū)別與方式
ThreadPoolExecutor的用法就不再累贅。
AsyncTaskExecutor主要是配合@Async使用,達(dá)到異步的作用。本來(lái)多線程就是異步的一種實(shí)現(xiàn)方式,這里的異步當(dāng)然也是多線程來(lái)實(shí)現(xiàn)。代碼示例如下:
@Component
public class TestAsyncMethod {
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Resource(name = "chainThreadPoolTaskExecutor")
private AsyncTaskExecutor asyncTaskExecutor;
public static Random random = new Random();
@Async("chainThreadPoolTaskExecutor")//指定線程池,可能容器中存在其他異步線程池
public void taskOne() throws Exception {
System.out.println("開(kāi)始執(zhí)行任務(wù)一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("任務(wù)一執(zhí)行時(shí)間:" + (end - start));
System.out.println("taskOne name = " + Thread.currentThread().getName());
}
// 省略 taskTwo() , taskThree()代碼 因與taskOne()類似
public void taskFour() throws Exception {
System.out.println("開(kāi)始執(zhí)行任務(wù)四");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("任務(wù)四執(zhí)行時(shí)間:" + (end - start));
System.out.println("taskFour name = " + Thread.currentThread().getName());
}
// 省略 taskFive() , taskSix()代碼 因與taskThree()類似
/*************************************************線程中存在返回值*************************************************************************/
@Async("mdcThreadPoolTaskExecutor")
public void taskSeven() throws Exception {
System.out.println("開(kāi)始執(zhí)行任務(wù)七");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
Future future = threadPoolExecutor.submit(() -> {
return getValue();
});
//阻塞當(dāng)前線程
System.out.println(future.get());
Future future1 = threadPoolExecutor.submit(() -> {
try {
if ((Integer) future.get() == 1) {
System.out.println("future1得到了future的結(jié)果");
}
System.out.println("future1結(jié)束");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println("任務(wù)七執(zhí)行時(shí)間:" + (end - start));
System.out.println("taskSeven name = " + Thread.currentThread().getName());
}
public int getValue() throws InterruptedException {
Thread.sleep(6000);
return 1;
}
}
測(cè)試方法與注釋
@Resource
private ThreadPoolExecutor threadPoolExecutor;
@Resource(name = "chainThreadPoolTaskExecutor")
private AsyncTaskExecutor asyncTaskExecutor;
/**
* 普通調(diào)用
* 在主線程中調(diào)用方法
* 4-5-6順序執(zhí)行,且均為當(dāng)前主線程
* 開(kāi)始執(zhí)行任務(wù)四
* 任務(wù)四執(zhí)行時(shí)間:670
* taskFour name = http-nio-8080-exec-1
* 開(kāi)始執(zhí)行任務(wù)五
* 任務(wù)五執(zhí)行時(shí)間:785
* taskFive name = http-nio-8080-exec-1
* 開(kāi)始執(zhí)行任務(wù)六
* 任務(wù)六執(zhí)行時(shí)間:590
* taskSix name = http-nio-8080-exec-1
* test1 name = http-nio-8080-exec-1
*/
public String test1() throws Exception {
testAsyncMethod.taskFour();
testAsyncMethod.taskFive();
testAsyncMethod.taskSix();//主線程等待456執(zhí)行完成繼續(xù)
System.out.println("test1 name = " + Thread.currentThread().getName());
return "over";
}
/**
* 在主線程中從線程池中拿出一個(gè)線程 執(zhí)行方法 主線程繼續(xù)向下執(zhí)行
* 三個(gè)任務(wù)為順序 同步執(zhí)行 使用的線程為同一個(gè)
* test2 name = http-nio-8080-exec-2
* 開(kāi)始執(zhí)行任務(wù)四
* 任務(wù)四執(zhí)行時(shí)間:485
* taskFour name = query-pool-5
* 開(kāi)始執(zhí)行任務(wù)五
* 任務(wù)五執(zhí)行時(shí)間:358
* taskFive name = query-pool-5
* 開(kāi)始執(zhí)行任務(wù)六
* 任務(wù)六執(zhí)行時(shí)間:881
* taskSix name = query-pool-5
*/
public String test2() throws Exception {
threadPoolExecutor.execute(()->{
try {
testAsyncMethod.taskFour();
testAsyncMethod.taskFive();
testAsyncMethod.taskSix();
} catch (Exception e) {
e.printStackTrace();
}
});//主線程繼續(xù)向下執(zhí)行 不等待上述線程
System.out.println("test2 name = " + Thread.currentThread().getName());
return "over";
}
/**
* 使三個(gè)任務(wù)異步執(zhí)行
* 實(shí)現(xiàn)效果為@Async
* test3 name = http-nio-8080-exec-7
* 開(kāi)始執(zhí)行任務(wù)四
* 開(kāi)始執(zhí)行任務(wù)五
* 開(kāi)始執(zhí)行任務(wù)六
* 任務(wù)四執(zhí)行時(shí)間:49
* taskFour name = query-pool-4
* 任務(wù)六執(zhí)行時(shí)間:510
* taskSix name = query-pool-14
* 任務(wù)五執(zhí)行時(shí)間:912
* taskFive name = query-pool-1
*/
public String test3() throws Exception {
threadPoolExecutor.execute(()->{
try {
testAsyncMethod.taskFour();
} catch (Exception e) {
e.printStackTrace();
}
});
threadPoolExecutor.execute(()->{
try {
testAsyncMethod.taskFive();
} catch (Exception e) {
e.printStackTrace();
}
});
threadPoolExecutor.execute(()->{
try {
testAsyncMethod.taskSix();
} catch (Exception e) {
e.printStackTrace();
}
});//主線程不等待上述線程
System.out.println("test3 name = " + Thread.currentThread().getName());
return "over";
}
/**
* 注解@Async使用
* 會(huì)開(kāi)三個(gè)線程
* 效果同test3
*/
public String test4() throws Exception {
testAsyncMethod.taskOne();
testAsyncMethod.taskTwo();
testAsyncMethod.taskThree();
System.out.println("test4 name = " + Thread.currentThread().getName());
return "over";
}
/********存在返回值調(diào)用*********/
/**
* test5 name = http-nio-8080-exec-3
* 開(kāi)始執(zhí)行任務(wù)七
* 1
* 任務(wù)七執(zhí)行時(shí)間:6004
* taskSeven name = Script-Executor4
* future1得到了future的結(jié)果
* future1結(jié)束
*/
public String testme5() throws Exception {
testAsyncMethod.taskSeven();//主線程不等待這個(gè)線程,繼續(xù)往下執(zhí)行
System.out.println("test5 name = " + Thread.currentThread().getName());
return "over";
}
/**
* 阻塞時(shí)間小于等于12秒
* @return
* @throws Exception
*/
public String testme6() throws Exception {
long start = System.currentTimeMillis();
//開(kāi)啟一個(gè)線程
Future future1=asyncTaskExecutor.submit(()->{
return testAsyncMethod.getValue();
});
//開(kāi)啟一個(gè)線程
Future future2=asyncTaskExecutor.submit(()->{
return testAsyncMethod.getValue()+1;
});
//未得到值前,主線程阻塞
System.out.println((Integer) future1.get() + (Integer) future2.get());//3
long end = System.currentTimeMillis();//理論上時(shí)間小于12秒 ---測(cè)試時(shí)間6000~6001 (ms)
return end-start +"";
}
/**
* 不難猜想與驗(yàn)證,如果未被@Async注解的方法被asyncTaskExecutor執(zhí)行,那么效果與threadPoolExecutor一樣。畢竟asyncTaskExecutor內(nèi)部就是threadPoolExecutor。
*/
上述代碼主要要注意注釋中的線程名。通過(guò)上述代碼其實(shí)就能分辨兩個(gè)線程池差別了。
值得注意的是,當(dāng)用asyncTaskExecutor去執(zhí)行未被@Async注解的方法,作用與threadPoolExecutor是一樣的。
大多情況兩種線程池可以混用,沒(méi)有區(qū)別。僅對(duì)于多個(gè)線程任務(wù)調(diào)度存在差異。
場(chǎng)景區(qū)別:如果業(yè)務(wù)函數(shù)task6()依賴task5()的值做計(jì)算,那么推薦使用threadPoolExecutor做同步。如果業(yè)務(wù)函數(shù)結(jié)果互不影響,如task5()去存數(shù)據(jù)庫(kù)表1,task6()去存數(shù)據(jù)庫(kù)表2,task7()去計(jì)算值,那么使用AsyncExecutor效果更好。若存在線程返回值,仍會(huì)阻塞調(diào)用future.get()的當(dāng)前線程,這就即線程中存在返回值且有依賴關(guān)系,如上面代碼test7。
四 其他
這篇有點(diǎn)灌水文的感覺(jué)了,but這個(gè)問(wèn)題分析與解決的過(guò)程還是有點(diǎn)僵硬,所以開(kāi)篇水文記錄一下。下一篇重新溫習(xí)一下 線程,異步,cpu調(diào)度...