springboot+線程池使用


關於線程和線程池的學習,我們可以從以下幾個方面入手:

  • 第一,什么是線程,線程和進程的區別是什么

  • 第二,線程中的基本概念,線程的生命周期

  • 第三,單線程和多線程

  • 第四,線程池的原理解析

  • 第五,常見的幾種線程池的特點以及各自的應用場景

一、什么是線程

線程,程序執行流的最小執行單位,是行程中的實際運作單位,經常容易和進程這個概念混淆。那么,線程和進程究竟有什么區別呢?首先,進程是一個動態的過程,是一個活動的實體。簡單來說,一個應用程序的運行就可以被看做是一個進程,而線程,是運行中的實際的任務執行者。可以說,進程中包含了多個可以同時運行的線程。

二、線程的生命周期

線程的生命周期可以利用以下的圖解來更好的理解:

第一步,是用new Thread()的方法新建一個線程,在線程創建完成之后,線程就進入了就緒(Runnable)狀態,此時創建出來的線程進入搶占CPU資源的狀態,當線程搶到了CPU的執行權之后,線程就進入了運行狀態(Running),當該線程的任務執行完成之后或者是非常態的調用的stop()方法之后,線程就進入了死亡狀態。而我們在圖解中可以看出,線程還具有一個則色的過程,這是怎么回事呢?當面對以下幾種情況的時候,容易造成線程阻塞,第一種,當線程主動調用了sleep()方法時,線程會進入則阻塞狀態,除此之外,當線程中主動調用了阻塞時的IO方法時,這個方法有一個返回參數,當參數返回之前,線程也會進入阻塞狀態,還有一種情況,當線程進入正在等待某個通知時,會進入阻塞狀態。那么,為什么會有阻塞狀態出現呢?我們都知道,CPU的資源是十分寶貴的,所以,當線程正在進行某種不確定時長的任務時,Java就會收回CPU的執行權,從而合理應用CPU的資源。我們根據圖可以看出,線程在阻塞過程結束之后,會重新進入就緒狀態,重新搶奪CPU資源。這時候,我們可能會產生一個疑問,如何跳出阻塞過程呢?又以上幾種可能造成線程阻塞的情況來看,都是存在一個時間限制的,當sleep()方法的睡眠時長過去后,線程就自動跳出了阻塞狀態,第二種則是在返回了一個參數之后,在獲取到了等待的通知時,就自動跳出了線程的阻塞過程

三、什么是單線程和多線程?

單線程,顧名思義即是只有一條線程在執行任務,這種情況在我們日常的工作學習中很少遇到,所以我們只是簡單做一下了解

多線程,創建多條線程同時執行任務,這種方式在我們的日常生活中比較常見。但是,在多線程的使用過程中,還有許多需要我們了解的概念。比如,在理解上並行和並發的區別,以及在實際應用的過程中多線程的安全問題,對此,我們需要進行詳細的了解。

並行和並發:在我們看來,都是可以同時執行多種任務,那么,到底他們二者有什么區別呢?

並發,從宏觀方面來說,並發就是同時進行多種時間,實際上,這幾種時間,並不是同時進行的,而是交替進行的,而由於CPU的運算速度非常的快,會造成我們的一種錯覺,就是在同一時間內進行了多種事情

而並發,則是真正意義上的同時進行多種事情。這種只可以在多核CPU的基礎下完成。

還有就是多線程的安全問題?為什么會造成多線程的安全問題呢?我們可以想象一下,如果多個線程同時執行一個任務,name意味着他們共享同一種資源,由於線程CPU的資源不一定可以被誰搶占到,這是,第一條線程先搶占到CPU資源,他剛剛進行了第一次操作,而此時第二條線程搶占到了CPU的資源,name,共享資源還來不及發生變化,就同時有兩條數據使用了同一條資源,具體請參考多線程買票問題。這個問題我們應該如何解決那?

有造成問題的原因我們可以看出,這個問題主要的矛盾在於,CPU的使用權搶占和資源的共享發生了沖突,解決時,我們只需要讓一條線程戰歌了CPU的資源時,阻止第二條線程同時搶占CPU的執行權,在代碼中,我們只需要在方法中使用同步代碼塊即可。在這里,同步代碼塊不多進行贅述,可以自行了解。

四,線程池

又以上介紹我們可以看出,在一個應用程序中,我們需要多次使用線程,也就意味着,我們需要多次創建並銷毀線程。而創建並銷毀線程的過程勢必會消耗內存。而在Java中,內存資源是及其寶貴的,所以,我們就提出了線程池的概念。

線程池:Java中開辟出了一種管理線程的概念,這個概念叫做線程池,從概念以及應用場景中,我們可以看出,線程池的好處,就是可以方便的管理線程,也可以減少內存的消耗。

那么,我們應該如何創建一個線程池那?Java中已經提供了創建線程池的一個類:Executor

而我們創建時,一般使用它的子類:ThreadPoolExecutor.

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue  workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)
這是其中最重要的一個構造方法,這個方法決定了創建出來的線程池的各種屬性,下面依靠一張圖來更好的理解線程池和這幾個參數:

又圖中,我們可以看出,線程池中的corePoolSize就是線程池中的核心線程數量,這幾個核心線程,只是在沒有用的時候,也不會被回收,maximumPoolSize就是線程池中可以容納的最大線程的數量,而keepAliveTime,就是線程池中除了核心線程之外的其他的最長可以保留的時間,因為在線程池中,除了核心線程即使在無任務的情況下也不能被清除,其余的都是有存活時間的,意思就是非核心線程可以保留的最長的空閑時間,而util,就是計算這個時間的一個單位,workQueue,就是等待隊列,任務可以儲存在任務隊列中等待被執行,執行的是FIFIO原則(先進先出)。threadFactory,就是創建線程的線程工廠,最后一個handler,是一種拒絕策略,我們可以在任務滿了知乎,拒絕執行某些任務。

線程池的執行流程又是怎樣的呢?

有圖我們可以看出,任務進來時,首先執行判斷,判斷核心線程是否處於空閑狀態,如果不是,核心線程就先就執行任務,如果核心線程已滿,則判斷任務隊列是否有地方存放該任務,若果有,就將任務保存在任務隊列中,等待執行,如果滿了,在判斷最大可容納的線程數,如果沒有超出這個數量,就開創非核心線程執行任務,如果超出了,就調用handler實現拒絕策略。

handler的拒絕策略:

有四種:第一種AbortPolicy:不執行新任務,直接拋出異常,提示線程池已滿

第二種DisCardPolicy:不執行新任務,也不拋出異常

第三種DisCardOldSetPolicy:將消息隊列中的第一個任務替換為當前新進來的任務執行

第四種CallerRunsPolicy:直接調用execute來執行當前任務

五,四種常見的線程池:

CachedThreadPool:可緩存的線程池,該線程池中沒有核心線程,非核心線程的數量為Integer.max_value,就是無限大,當有需要時創建線程來執行任務,沒有需要時回收線程,適用於耗時少,任務量大的情況。

SecudleThreadPool:周期性執行任務的線程池,按照某種特定的計划執行線程中的任務,有核心線程,但也有非核心線程,非核心線程的大小也為無限大。適用於執行周期性的任務。

SingleThreadPool:只有一條線程來執行任務,適用於有順序的任務的應用場景。

FixedThreadPool:定長的線程池,有核心線程,核心線程的即為最大的線程數量,沒有非核心線程

六,線程池

1、配置


@Configuration
@EnableAsync
public class TaskPoolConfig {

    @Bean("taskExecutor")
    public Executor taskExecutro(){
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("taskExecutor--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        return taskExecutor;
    }


}

2、使用


@Component
public class AsyncTask {

    @Async("taskExecutor")
    public void tesTask(int i){
        System.out.println(Thread.currentThread().getName()+"-----"+i);
    }

    @Async("taskExecutor")
    public void stringTask(String str){
      
        System.out.println(Thread.currentThread().getName()+str);
    }
}


免責聲明!

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



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