深入理解執行緒和執行緒池(圖文詳解)
關於執行緒和執行緒池的學習,我們可以從以下幾個方面入手:
第一,什麼是執行緒,執行緒和程序的區別是什麼
第二,執行緒中的基本概念,執行緒的生命週期
第三,單執行緒和多執行緒
第四,執行緒池的原理解析
第五,常見的幾種執行緒池的特點以及各自的應用場景
一、
執行緒,程式執行流的最小執行單位,是行程中的實際運作單位,經常容易和程序這個概念混淆。那麼,執行緒和程序究竟有什麼區別呢?首先,程序是一個動態的過程,是一個活動的實體。簡單來說,一個應用程式的執行就可以被看做是一個程序,而執行緒,是執行中的實際的任務執行者。可以說,程序中包含了多個可以同時執行的執行緒。
二、
執行緒的生命週期,執行緒的生命週期可以利用以下的圖解來更好的理解:
第一步,是用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<Runnable> 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:定長的執行緒池,有核心執行緒,核心執行緒的即為最大的執行緒數量,沒有非核心執行緒