實(shí)習(xí)日志-線程池再研究

聲明:涉及到隱私問(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)系如下:

image.png

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)度...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容