同步異步 定時任務 Spring線程池


一、使用線程池的目的:處理異步任務(雖然有同步線程池SyncTaskExecutor,但是本質不算一個線程池,只有同步操作,沒有異步調用)

 

二、同步與異步區別

  同步:同步就是整個處理過程順序執行,當各個過程都執行完畢,並返回結果。

  異步:異步調用則是只是發送了調用的指令,調用者無需等待被調用的方法完全執行完畢;而是繼續執行下面的流程。例如, 在某個調用中,需要順序調用 A, B, C三個過程方法;如他們都是同步調用,則需要將他們都順序執行完畢之后,方算作過程執行完畢;如B為一個異步的調用方法,則在執行完A之后,調用B,並不等待B完成,而是執行開始調用C,待C執行完畢之后,就意味着這個過程執行完畢了。在Java中,一般在處理類似的場景之時,都是基於創建獨立的線程去完成相應的異步調用邏輯,通過主線程和不同的業務子線程之間的執行流程,從而在啟動獨立的線程之后,主線程繼續執行而不會產生停滯等待的情況。

 

三、異步任務使用:@EnableAsync @Async("線程池名")

  1. 注意事項:

    a. 在Spring啟動類上添加注解@EnableAsyn或者在你們線程池配置類添加@EnableAsyn(任選其一即可)

    b. @Async不指定value則使用默認異步線程池

    c. 使用此注解的方法的類對象,需要是spring管理下的bean對象(@Component)

  2. Spring默認的異步線程池通過實現AsyncConfigurer接口進行配置

public interface AsyncConfigurer {
 
    Executor getAsyncExecutor();
    
    AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler();
 
}

  Executor : 處理異步方法調用時要使用的實例,

  AsyncUncaughtExceptionHandler :在使用void返回類型的異步方法執行期間拋出異常時要使用的實例。

  3. 實現舉例:

@Configuration
public class ThreadPoolConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        // 設置核心線程數
        threadPool.setCorePoolSize(100);
        // 設置最大線程數
        threadPool.setMaxPoolSize(200);
        // 線程池所使用的緩沖隊列
        threadPool.setQueueCapacity(50);
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        // 等待任務在關機時完成--表明等待所有線程執行完
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 等待時間 (默認為0,此時立即停止),並沒等待xx秒后強制停止
        threadPool.setAwaitTerminationSeconds(60);
        // 線程名稱前綴
        threadPool.setThreadNamePrefix("Derry-Async-");
        // 初始化線程
        threadPool.initialize();

        return threadPool;
    }

    /**
     * 異步異常處理
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

 4. Spring 已經實現的線程池

  • SimpleAsyncTaskExecutor:不是真的線程池,這個類不重用線程,默認每次調用都會創建一個新的線程。

  • SyncTaskExecutor:這個類沒有實現異步調用,只是一個同步操作。只適用於不需要多線程的地方。

  • ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類。

  • SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的類。線程池同時被quartz和非quartz使用,才需要使用此類。

  • ThreadPoolTaskExecutor :最常使用,推薦。其實質是對java.util.concurrent.ThreadPoolExecutor的包裝。

 5. 自定義線程池:

@Component
public class ThreadPool1 {

    //不指定Bean的name則按照方法名創建Bean注入容器
    @Bean(name="myAsync")
    public Executor myAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //最大線程數
        executor.setMaxPoolSize(100);
        //核心線程數
        executor.setCorePoolSize(10);
        //任務隊列的大小
        executor.setQueueCapacity(10);
        //線程前綴名
        executor.setThreadNamePrefix("My-Async-ThreadPool-1-");
        //線程存活時間
        executor.setKeepAliveSeconds(30);
        // 等待時間 (默認為0,此時立即停止),並沒等待xx秒后強制停止
        executor.setAwaitTerminationSeconds(60);
        //等待任務在關機時完成--表明等待所有線程執行完
        executor.setWaitForTasksToCompleteOnShutdown(true);

        /**
         * 拒絕處理策略
         * CallerRunsPolicy():交由調用方線程運行,比如 main 線程。
         * AbortPolicy():直接拋出異常。
         * DiscardPolicy():直接丟棄。
         * DiscardOldestPolicy():丟棄隊列中最老的任務。
         */

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        //線程初始化
        executor.initialize();

        return executor;
    }
}

  自定義線程池需要給@Async指定value值

 6. 使用多個線程池進行不同功能的線程隔離

  使用多個線程池,把相同的任務放到同一個線程池中,可以起到隔離的作用,避免有線程出錯時影響到其他線程池,例如只有一個線程池時,有兩種任務,下單,處理圖片,如果線程池被處理圖片的任務占滿,影響下單任務的進行

 

四、定時任務@EnableScheduling @Scheduled

 說明:

  a. @Scheduled 任務調度注解,主要用於配置定時任務;springboot默認的調度器線程池大小為 1(也就是說在多個方法上加上@schedule的,多個定時任務默認是加入延時隊列依次同步執行的)

  b. 在定時任務的方法上加上@Async就會把該定時任務交由異步線程池執行

  配置默認調度器線程池的方法實現SchedulingConfigurer 接口

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(setTaskExecutors());
    }

    @Bean(destroyMethod="shutdown")
    public Executor setTaskExecutors(){
        return Executors.newScheduledThreadPool(3); // 3個線程來處理。
    }
}

 

五、代碼演示

 1. 定時任務演示 (為方便演示線程同步執行關系,調度器線程池大小已經配置為3)

//    @Async
    @Scheduled(fixedDelay = 1000*3)  
    public void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"開始執行定時任務");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n');
    }

  運行結果:

pool-1-thread-1開始執行定時任務
pool-1-thread-1定時任務執行完畢

pool-1-thread-2開始執行定時任務
pool-1-thread-2定時任務執行完畢

該方法的定時任務3s執行一次,但是每次執行要睡眠5s,由於未開啟異步所以需要等上一個線程執行完畢,下一個線程才能再次進入該方法。

 2.加上@Async(未指定具體線程池,定時任務交由默認異步線程池執行)

    @Async
    @Transactional
    @Scheduled(fixedDelay = 1000*3)
    public void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"開始執行定時任務");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n');
    }

  運行結果:

Default-Async-ThreadPool-1開始執行定時任務
Default-Async-ThreadPool-2開始執行定時任務  //線程2在線程1運行期間便執行第二次定時任務,此即異步
Default-Async-ThreadPool-1定時任務執行完畢

Default-Async-ThreadPool-3開始執行定時任務  //線程3在線程2運行期間便執行第三次定時任務
Default-Async-ThreadPool-2定時任務執行完畢

Default-Async-ThreadPool-4開始執行定時任務  
Default-Async-ThreadPool-3定時任務執行完畢

 3. 加上@Async並指定具體線程池

    @Async("myAsync")
    @Transactional
    @Scheduled(fixedDelay = 1000*3)
    public void test2() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"開始執行定時任務");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n');
    }

  運行結果:

My-Async-ThreadPool-1-1開始執行定時任務  //線程名稱發生變化,可見自定義線程池是生效的
My-Async-ThreadPool-1-2開始執行定時任務
My-Async-ThreadPool-1-1定時任務執行完畢

My-Async-ThreadPool-1-3開始執行定時任務
My-Async-ThreadPool-1-2定時任務執行完畢

My-Async-ThreadPool-1-4開始執行定時任務
My-Async-ThreadPool-1-3定時任務執行完畢

 

 4.多線程池隔離異步執行不同定時任務

    @Async //使用默認異步線程池
    @Scheduled(fixedDelay = 1000*3)
    public void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"開始執行定時任務");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n');
    }

    @Async("myAsync") //使用自定義異步線程池
    @Scheduled(fixedDelay = 1000*3)
    public void test2() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"開始執行定時任務");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n');
    }

  運行結果:

Default-Async-ThreadPool-1開始執行定時任務
My-Async-ThreadPool-1-1開始執行定時任務
Default-Async-ThreadPool-2開始執行定時任務
My-Async-ThreadPool-1-2開始執行定時任務
Default-Async-ThreadPool-1定時任務執行完畢

My-Async-ThreadPool-1-1定時任務執行完畢

Default-Async-ThreadPool-3開始執行定時任務
My-Async-ThreadPool-1-3開始執行定時任務
Default-Async-ThreadPool-2定時任務執行完畢

Default-Async-ThreadPool-4開始執行定時任務
My-Async-ThreadPool-1-4開始執行定時任務
My-Async-ThreadPool-1-2定時任務執行完畢

 六、相關隨筆

  1. Spring最常用的線程池類型ThreadPoolTaskExecutor參數原理詳解:https://www.cnblogs.com/JNU-Iot-Longxin/p/15305313.html

  2. ThreadPoolTaskExecutor另一種使用方式(不推薦):https://www.cnblogs.com/JNU-Iot-Longxin/p/15305050.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM