天天看點

Java并發程式設計的藝術(十)——線程池(1)

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/qq_34173549/article/details/79612287

線程池的作用

  1. 減少資源的開銷 

    減少了每次建立線程、銷毀線程的開銷。

  2. 提高響應速度 

    每次請求到來時,由于線程的建立已經完成,故可以直接執行任務,是以提高了響應速度。

  3. 提高線程的可管理性 

    線程是一種稀缺資源,若不加以限制,不僅會占用大量資源,而且會影響系統的穩定性。 

    是以,線程池可以對線程的建立與停止、線程數量等等因素加以控制,使得線程在一種可控的範圍内運作,不僅能保證系統穩定運作,而且友善性能調優。

線程池的實作原理

線程池一般由兩種角色構成:多個工作線程 和 一個阻塞隊列。

  • 工作線程 

    工作線程是一組已經處在運作中的線程,它們不斷地向阻塞隊列中領取任務執行。

  • 阻塞隊列 

    阻塞隊列用于存儲工作線程來不及處理的任務。當工作線程都在執行任務時,到來的新任務就隻能暫時在阻塞隊列中存儲。

ThreadPoolExecutor的使用

建立線程池

通過如下代碼即可建立一個線程池:

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnableTaskQueue, handler);           
  • 1
  • corePoolSize:基本線程數量 

    它表示你希望線程池達到的一個值。線程池會盡量把實際線程數量保持在這個值上下。

  • maximumPoolSize:最大線程數量 

    這是線程數量的上界。 

    如果實際線程數量達到這個值:

    1. 阻塞隊列未滿:任務存入阻塞隊列等待執行
    2. 阻塞隊列已滿:調用飽和政策
  • keepAliveTime:空閑線程的存活時間 

    當實際線程數量超過corePoolSize時,若線程空閑的時間超過該值,就會被停止。 

    PS:當任務很多,且任務執行時間很短的情況下,可以将該值調大,提高線程使用率。

  • timeUnit:keepAliveTime的機關
  • runnableTaskQueue:任務隊列 

    這是一個存放任務的阻塞隊列,可以有如下幾種選擇: 

    1. ArrayBlockingQueue 

      它是一個由數組實作的阻塞隊列,FIFO。

    2. LinkedBlockingQueue 

      它是一個由連結清單實作的阻塞隊列,FIFO。 

      吞吐量通常要高于ArrayBlockingQueue。 

      fixedThreadPool使用的阻塞隊列就是它。 

      它是一個無界隊列。

    3. SynchronousQueue 

      它是一個沒有存儲空間的阻塞隊列,任務送出給它之後必須要交給一條工作線程處理;如果目前沒有空閑的工作線程,則立即建立一條新的工作線程。 

      cachedThreadPool用的阻塞隊列就是它。 

    4. PriorityBlockingQueue 

      它是一個優先權阻塞隊列。

  • handler:飽和政策 

    當實際線程數達到maximumPoolSize,并且阻塞隊列已滿時,就會調用飽和政策。 

    JDK1.5由四種飽和政策: 

    1. AbortPolicy 

      預設。直接抛異常。

    2. CallerRunsPolicy 

      隻用調用者所在的線程執行任務。

    3. DiscardOldestPolicy 

      丢棄任務隊列中最久的任務。

    4. DiscardPolicy 

      丢棄目前任務。

送出任務

可以向ThreadPoolExecutor送出兩種任務:Callable和Runnable。

  1. Callable 

    該類任務有傳回結果,可以抛出異常。 

    通過submit函數送出,傳回Future對象。 

    可通過get擷取執行結果。

  2. Runnable 

    該類任務隻執行,無法擷取傳回結果,并在執行過程中無法抛異常。 

    通過execute送出。

關閉線程池

關閉線程池有兩種方式:shutdown和shutdownNow,關閉時,會周遊所有的線程,調用它們的interrupt函數中斷線程。但這兩種方式對于正在執行的線程處理方式不同。

  1. shutdown() 

    僅停止阻塞隊列中等待的線程,那些正在執行的線程就會讓他們執行結束。

  2. shutdownNow() 

    不僅會停止阻塞隊列中的線程,而且會停止正在執行的線程。

ThreadPoolExecutor運作機制

當有請求到來時:

  1. 若目前實際線程數量 少于 corePoolSize,即使有空閑線程,也會建立一個新的工作線程;
  2. 若目前實際線程數量處于corePoolSize和maximumPoolSize之間,并且阻塞隊列沒滿,則任務将被放入阻塞隊列中等待執行;
  3. 若目前實際線程數量 小于 maximumPoolSize,但阻塞隊列已滿,則直接建立新線程處理任務;
  4. 若目前實際線程數量已經達到maximumPoolSize,并且阻塞隊列已滿,則使用飽和政策。

設定合理的線程池大小

任務一般可分為:CPU密集型、IO密集型、混合型,對于不同類型的任務需要配置設定不同大小的線程池。

  • CPU密集型任務 

    盡量使用較小的線程池,一般為CPU核心數+1。 

    因為CPU密集型任務使得CPU使用率很高,若開過多的線程數,隻能增加上下文切換的次數,是以會帶來額外的開銷。

  • IO密集型任務 

    可以使用稍大的線程池,一般為2*CPU核心數。 

    IO密集型任務CPU使用率并不高,是以可以讓CPU在等待IO的時候去處理别的任務,充分利用CPU時間。

  • 混合型任務 

    可以将任務分成IO密集型和CPU密集型任務,然後分别用不同的線程池去處理。 

    隻要分完之後兩個任務的執行時間相差不大,那麼就會比串行執行來的高效。 

    因為如果劃分之後兩個任務執行時間相差甚遠,那麼先執行完的任務就要等後執行完的任務,最終的時間仍然取決于後執行完的任務,而且還要加上任務拆分與合并的開銷,得不償失。