【SpringBoot基礎知識】如何在springboot中使用多線程

1.楔子
在我們的系統中,經常會處理一些耗時任務,自然而然的會想到使用多線程,JDK給我們提供了非常方便的操作線程的API,爲什麼還要使用Spring來實現多線程呢?

1.使用Spring比使用JDK原生的併發API更簡單。(一個註解@Async就搞定)
2.我們的應用環境一般都會集成Spring,我們的Bean也都交給Spring來進行管理,那麼使用Spring來實現多線程更加簡單,更加優雅。

爲什麼要用異步?當需要調用多個服務時,使用傳統的同步調用來執行時,是這樣的

調用服務A
等待服務A的響應
調用服務B
等待服務B的響應
調用服務C
等待服務C的響應
根據從服務A、服務B和服務C返回的數據完成業務邏輯,然後結束

如果每個服務需要3秒的響應時間,這樣順序執行下來,可能需要9秒以上才能完成業務邏輯,但是如果我們使用異步調用

調用服務A
調用服務B
調用服務C
然後等待從服務A、B和C的響應
根據從服務A、服務B和服務C返回的數據完成業務邏輯,然後結束

理論上 3秒左右即可完成同樣的業務邏輯

2. spring boot 如何使用多線程
Spring中實現多線程,其實非常簡單,只需要在配置類中添加@EnableAsync就可以使用多線程。在希望執行的併發方法中使用@Async就可以定義一個線程任務。通過spring給我們提供的ThreadPoolTaskExecutor就可以使用線程池。

2.1第一步,先在Spring Boot主類中定義一個線程池,比如:

@Configuration
@EnableAsync  // 啓用異步任務
public class AsyncConfiguration {

    // 聲明一個線程池(並指定線程池的名字)
    @Bean("taskExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心線程數5:線程池創建時候初始化的線程數
        executor.setCorePoolSize(5);
        //最大線程數5:線程池最大的線程數,只有在緩衝隊列滿了之後纔會申請超過核心線程數的線程
        executor.setMaxPoolSize(5);
        //緩衝隊列500:用來緩衝執行任務的隊列
        executor.setQueueCapacity(500);
        //允許線程的空閒時間60秒:當超過了核心線程出之外的線程在空閒時間到達之後會被銷燬
        executor.setKeepAliveSeconds(60);
        //線程池名的前綴:設置好了之後可以方便我們定位處理任務所在的線程池
        executor.setThreadNamePrefix("DailyAsync-");
        executor.initialize();
        return executor;
    }
}


有很多你可以配置的東西。默認情況下,使用SimpleAsyncTaskExecutor。

2.2第二步,使用線程池
在定義了線程池之後,我們如何讓異步調用的執行任務使用這個線程池中的資源來運行呢?方法非常簡單,我們只需要在@Async註解中指定線程池名即可,比如:

@Service
public class GitHubLookupService {

    private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);

    @Autowired
    private RestTemplate restTemplate;

    // 這裏進行標註爲異步任務,在執行此方法的時候,會單獨開啓線程來執行(並指定線程池的名字)
    @Async("taskExecutor")
    public CompletableFuture<String> findUser(String user) throws InterruptedException {
        logger.info("Looking up " + user);
        String url = String.format("https://api.github.com/users/%s", user);
        String results = restTemplate.getForObject(url, String.class);
        // Artificial delay of 3s for demonstration purposes
        Thread.sleep(3000L);
        return CompletableFuture.completedFuture(results);
    }
}


findUser 方法被標記爲Spring的 @Async 註解,表示它將在一個單獨的線程上運行。該方法的返回類型是 CompleetableFuture 而不是 String,這是任何異步服務的要求。

2.3第三步,單元測試
最後,我們來寫個單元測試來驗證一下

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class AsyncTests {
    private static final Logger logger = LoggerFactory.getLogger(AsyncTests.class);

    @Autowired
    private GitHubLookupService gitHubLookupService;

    @Test
    public void asyncTest() throws InterruptedException, ExecutionException {
        // Start the clock
        long start = System.currentTimeMillis();

        // Kick of multiple, asynchronous lookups
        CompletableFuture<String> page1 = gitHubLookupService.findUser("PivotalSoftware");
        CompletableFuture<String> page2 = gitHubLookupService.findUser("CloudFoundry");
        CompletableFuture<String> page3 = gitHubLookupService.findUser("Spring-Projects");

        // Wait until they are all done
        //join() 的作用:讓“主線程”等待“子線程”結束之後才能繼續運行
        CompletableFuture.allOf(page1,page2,page3).join();

        // Print results, including elapsed time
        float exc = (float)(System.currentTimeMillis() - start)/1000;
        logger.info("Elapsed time: " + exc + " seconds");
        logger.info("--> " + page1.get());
        logger.info("--> " + page2.get());
        logger.info("--> " + page3.get());
    }

}


執行上面的單元測試,我們可以在控制檯中看到所有輸出的線程名前都是之前我們定義的線程池前綴名開始的,並且執行時間小於9秒,說明我們使用線程池來執行異步任務的試驗成功了!


3. 注意事項
在使用spring的異步多線程時經常回碰到多線程失效的問題,解決方式爲:
異步方法和調用方法一定要寫在不同的類中 ,如果寫在一個類中,是沒有效果的!
原因:

spring對@Transactional註解時也有類似問題,spring掃描時具有@Transactional註解方法的類時,是生成一個代理類,由代理類去開啓關閉事務,而在同一個類中,方法調用是在類體內執行的,spring無法截獲這個方法調用。
————————————————
版權聲明:本文爲CSDN博主「jieniyimiao」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u013467442/article/details/89366155

 

--------------------------------------------------------------------------------------------------------------------

可能會出現RestTemplate自動注入失敗的問題,如是,看下面:

出現錯誤

Consider defining a bean of type 'org.springframework.web.client.RestTemplate' in your configuration.
No qualifying bean of type [org.springframework.web.client.RestTemplate] found

原因

Spring Boot<=1.3 無需定義,Spring Boot自動爲您定義了一個。
 
Spring Boot >= 1.4 Spring Boot不再自動定義一個RestTemplate,而是定義了一個RestTemplateBuilder允許您更好地控制所RestTemplate創建的對象

 定義RestTemplate Bean即可完成注入

@Configuration
public class ApplicationConfig {
 
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

 

 

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