天天看點

線程池在springboot中的使用

線程池的概念

線程池是線程的一個緩存,可以提供線程重用,避免了線程重複地進行建立銷毀,更加科學地管理線程,對線程進行統一地優化,排程和監控。

适用于

任務時間短、任務量大。

如果一個線程任務時間非常 長,可以忽略到線程的建立和銷毀時間,那麼使用線程池反而不合适

阻塞隊列概念

阻塞隊列

在任意時刻,不管并發有多高,隻有一個一個線程能夠進行隊列的入隊或者出隊操作。線程安全。

隊列滿,隻能進行出隊,入隊被阻塞。

隊列空,隻能進行入隊,出隊被阻塞。

線程池的幾個重要參數

  1. corePoolSize:核心線程數

    核心線程會一直存活,即使沒有任務需要執行。

    2 . maximumPoolSize 線程池最大線程數量

    3 . keepAliveTime 空閑線程存活時間

    一個線程如果處于空閑狀态,并且目前的線程數量大于corePoolSize,那麼在指定時間後,這個空閑線程會被銷毀,這裡的指定時間由keepAliveTime來設定

  2. queueCapacity:任務隊列容量(阻塞隊列)

    當線程數達到最大時,新任務會放在隊列中排隊等待執行。

線程池的執行順序

線程池按以下行為執行任務

  1. 當線程數小于核心線程數時,建立線程。
  2. 當線程數大于等于核心線程數,且任務隊列未滿時,将任務放入任務隊列。
  3. 當線程數大于等于核心線程數,且任務隊列已滿

    若線程數小于最大線程數,建立線程

    若線程數等于最大線程數,抛出異常,拒絕任務

    是以并不能保證先來的先執行

    線程池在springboot中的使用

線程池的五種狀态

Running

能接受新任務以及處理已經添加的任務

Shutdown

不接受新任務,但可以處理已經添加的任務

Stop

不接受新任務,不處理已經添加的任務,并且中斷正在執行的任務

Tidying

所有任務已經終止

Terminated

線程池徹底終止

Spring中線程池

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

線程池ThreadPoolExecutor,它的執行規則如下

線程池在springboot中的使用

在Springboot中對其進行了簡化處理,隻需要配置一個類型為java.util.concurrent.TaskExecutor或其子類的bean,并在配置類或直接在程式入口類上聲明注解@EnableAsync。

調用也簡單,在由Spring管理的對象的方法上标注注解@Async,顯式調用即可生效。

第一步,先在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

第二步,使用線程池

在定義了線程池之後,我們隻需要在@Async注解中指定線程池名即可,比如:

例子:

```java
@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,這是任何異步服務的要求。


#### 高并發下線程池配置
線程池大小配置
一般根據任務類型進行區分, 假設CPU為N核

CPU密集型任務需要減少線程數量, 降低線程切換開銷,可配置線程池大小為N + 1.
IO密集型任務則可以加大線程數量, 可配置線程池大小為 N * 2.
混合型任務則可以拆分為CPU密集型與IO密集型, 獨立配置.

CPU密集型:
                例如,一般我們系統的靜态資源,比如js,css等,會存在一個版本号,如 main.js?v0,每當使用者通路這個資源的時候,會發送一個比對請求到服務端,比對本地靜态檔案版本和服務端的檔案版本是否一緻,不一緻則更新.這種任務一般不占用大量IO,是以背景伺服器可以快速處理,壓力落在CPU上.

I/O密集型
                1個線程對應1個方法棧,線程的生命周期與方法棧相同.
比如某個線程的方法棧對應的入站順序為:controller()->service()->DAO(),由于DAO長時間的I/O操作,導緻該線程一直處于工作隊列,但它又不占用CPU,則此時有1個CPU是處于空閑狀态的.
                是以,這種情況下,應該加大線程池工作隊列的長度(如果CPU排程算法使用的是FCFS,則無法切換),盡量不讓CPU空閑下來,提高CPU使用率.


## 實戰

啟動類添加@EnableAsync , 表示開啟異步任務

使用@Configuration  配置一個線程池

           
@Configuration
@EnableAsync
public class AsynConfig {

    @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;
    }

}
           

給異步方法@Async(“taskExcutor”)

@Async("taskExecutor")
public Future<Map<String, Object>> query(User d, HttpServletRequest request) {
    // TODO Auto-generated method stub
    //省略邏輯
    Map<String,Object> map = new HashMap<>();
    return new AsyncResult<>(map);
}

異步回調  給方法外面包一層 Future<>  傳回值 AsyncResult

回調方法調用AsynResult.get()方法

get方法會阻塞到調用結束

resultMap.put("query", quary.get(10, TimeUnit.SECONDS));


           
### 注意點
1.注解的方法必須是public方法。
2.方法一定要從另一個類中調用,也就是從類的外部調用,類的内部調用是無效的。
3.如果需要從類的内部調用,需要先擷取其代理類,下面上代碼

```java
@Service
public class XxxService{
  public void methodA(){
    ...
    XxxService xxxServiceProxy = SpringUtil.getBean(XxxService.class);
    xxxServiceProxy.methodB();
    ...
  }
 
  @Async
  public void methodB() {
    ...
  }
}
           

代理工具類我也貼在下面

@Component
    public class SpringBeanUtil implements ApplicationContextAware {

        private static ApplicationContext applicationContext;

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if (SpringBeanUtil.applicationContext == null) {
                SpringBeanUtil.applicationContext = applicationContext;
            }
        }

        public static ApplicationContext getApplicationContext(){
            return applicationContext;
        }


        public static Object getBean(String name) {
            return applicationContext.getBean(name);
        }


        public static <T> T getBean(Class<T> clazz) {
            return applicationContext.getBean(clazz);
        }

    }

           
ReportService beanProxy = SpringBeanUtil.getBean(ReportService.class);
Future<JSONObject> reportAsync = beanProxy.getReportAsync(patCardType, patCardNo, beginDate, endDate);
           

這樣就可以在同一個類中調用異步方法