SpringBoot中使用註解方式進行多線程異步(學習筆記2020.3.30)

SpringBoot(Spring)中使用註解方式進行多線程異步(學習筆記2020.3.30)

前言:

以前的項目實現異步多線程大多是使用代碼配置好線程池進行代碼方式調用。

而現在Spring提供了註解方式開啓異步處理。(Annotation支持調度和異步執行)

要啓用對@Async 註釋的支持,可以將@EnableAsync添加到其中一個@Configuration classes 中。

1. 創建一個SpringBoot項目

1.1 依賴:

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>
    <artifactId>springboot-asynchronous</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

1.2 在啓動類上啓用異步註解

@SpringBootApplication
@EnableAsync //啓用異步
public class ApplicationAsynchronous {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationAsynchronous.class,args);
    }
}

1.3 創建一個給框架管理的bean

裏面有一個是註解標記的異步方法, 一個是同步方法, 然後進行調用看調用順序與線程名稱。

@Component
public class Asynchronous {
    @Async
    public void asynchronous1(String name) throws InterruptedException {
        Thread.sleep(2000);
        System.out.println("異步睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
    }
    
    public void commonMethod1(String name) throws InterruptedException {
        Thread.sleep(2000);
        System.out.println("普通睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
    }
}

1.4 進行測試

@SpringBootTest
@RunWith(SpringRunner.class)
public class AsynchronousTest {

    @Autowired
    private Asynchronous asynchronous;
    
    @Test
    public void asynchronousTest() throws InterruptedException {
        System.out.println("異步方法準備執行前");
        asynchronous.asynchronous1("張韶涵1");
        System.out.println("異步方法執行後");
        asynchronous.commonMethod1("張韶涵2");
        System.out.println("同步後方法準備執行後");
    }
}

輸出的結果爲: (可以看出異步方法執行了並不用等待返回結果, 在執行下一步)

異步方法準備執行前
異步方法執行後   
普通睡2秒後執行張韶涵2線程名稱:main
同步後方法準備執行後
異步睡2秒後執行張韶涵1線程名稱:task-1

因爲異步的原因,程序並沒有被sleep方法阻塞,這就是異步調用的好處。同時異步方法內部會新啓一個線程來執行,這裏線程名稱爲task - 1

默認情況下的異步線程池配置使得線程不能被重用,每次調用異步方法都會新建一個線程,我們可以自己定義異步線程池來優化。

1.5 修改默認異步線程池application.yml

修改application文件:

spring:
  task:
    execution:
      pool:
        max-size: 32 #最大線程數
        core-size: 32 #核心線程數, 默認8
        keep-alive: 10s #線程最大空閒時間, 默認值60
        allow-core-thread-timeout: true #允許核心線程超時 默認值true
        queue-capacity: 100 #緩衝隊列大小
      thread-name-prefix: mythread- #線程名
      shutdown:
        await-termination: true #是否等待所有線程執行完畢才關閉線程池,默認值爲false。
        await-termination-period: 60 #waitForTasksToCompleteOnShutdown的等待的時長,默認值爲0,即不等待。

輸出的結果爲:

異步方法準備執行前
異步方法執行後
普通睡2秒後執行張韶涵2線程名稱:main
同步後方法準備執行後
異步睡2秒後執行張韶涵1線程名稱:mythread-1

1.6 處理異步回調

如果異步方法具有返回值的話,需要使用Future來接收回調值。 修改asynchronous1方法

	@Async
    public Future<String> asynchronous1(String name) throws InterruptedException {
        Thread.sleep(2000);
        System.out.println("異步睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
        return new AsyncResult<>("異步返回結果"+name);
    }

運行測試:

	@Test //
    public void asynchronousTest() throws Exception {
        System.out.println("異步方法準備執行前");
        String name = asynchronous.asynchronous1("張韶涵1").get();
        System.out.println("異步方法執行後"+name);
        asynchronous.commonMethod1("張韶涵2");
        System.out.println("同步後方法準備執行後");
    }

結果: 可以看出調用了get()後線程會阻塞到獲取執行結果, 在往下執行。

get還有一個get(long timeout, TimeUnit unit)重載方法,我們可以通過這個重載方法設置超時時間,即異步方法在設定時間內沒有返回值的話,直接拋出java.util.concurrent.TimeoutException異常。

異步方法準備執行前
異步睡2秒後執行張韶涵1線程名稱:mythread-1
異步方法執行後異步返回結果張韶涵1
普通睡2秒後執行張韶涵2線程名稱:main
同步後方法準備執行後

比如: 設置10秒後沒有返回結果叫拋出異常!

String name = asynchronous.asynchronous1("張韶涵1").get(10, TimeUnit.SECONDS);

擴展資料:

使用配置類方式自定義配置線程池:

@Configuration
public class AsyncPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(200);
        executor.setQueueCapacity(25);
        executor.setKeepAliveSeconds(200);
        executor.setThreadNamePrefix("mythread-");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
		//當沒有線程可以被使用時的處理策略(拒絕任務),默認策略爲abortPolicy
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
  1. CallerRunsPolicy:用於被拒絕任務的處理程序,它直接在 execute 方法的調用線程中運行被拒絕的任務;如果執行程序已關閉,則會丟棄該任務。
  2. AbortPolicy:直接拋出java.util.concurrent.RejectedExecutionException異常。
  3. DiscardOldestPolicy:當線程池中的數量等於最大線程數時、拋棄線程池中最後一個要執行的任務,並執行新傳入的任務。
  4. DiscardPolicy:當線程池中的數量等於最大線程數時,不做任何動作。

要使用該線程池,只需要在@Async註解上指定線程池Bean名稱即可:

	@Async("asyncThreadPoolTaskExecutor")
    public Future<String> asynchronous1(String name) throws InterruptedException {
        Thread.sleep(2000);
        System.out.println("異步睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
        return new AsyncResult<>("異步返回結果"+name);
    }

1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章