首頁  >  文章  >  Java  >  Java執行緒池的原理、使用以及效能最佳化方法有哪些?

Java執行緒池的原理、使用以及效能最佳化方法有哪些?

PHPz
PHPz轉載
2023-05-09 19:10:061166瀏覽

1、什麼是執行緒及執行緒池

執行緒是作業系統進行時序調度的基本單元。

執行緒池可以理解為一個存在執行緒的池子,就是一個容器,這個容器只能存在執行緒。這個容器有大小,可以放7,8個,也可以放3,4個。也可以把容器裝滿,但是都有一個最大值,比如說12個。比如說我這邊線程池通常都裝5個線程,最多可以裝12個。這時候有五個人需要使用線程池,他就拿走了5個線程,然後在來兩個人怎麼辦,他肯定沒有線程可以使用,他必須等著那5個人使用完才行。但是我的池子能裝12個,我只有5個線程怎麼行,我肯定還得在裝幾個線程,要不然人再多一點就不夠了,這時候來了2個,我在生產2個線程,總數到7個,這個時候剩下2個人就不需要等待了,就可以直接使用。如果在來6個人呢,這個時候,我的池子裡面可能只剩下5個線程的容量了,我能在生產5個線程但是,還有一個人得在哪裡等著才行。我也不能讓人家漫無目的的等著啊,我找5個凳子吧,你們坐那等著,然後第一波五個人用完線程結束了,一下子騰出來了5個線程,剩下的一個人可以使用線程,這個時候依序又來了10個人,我的線程只有4個人可以使用,位置能坐五個人,剩下一個人怎麼辦,要不直接拒絕,我這邊沒有位,你要不先去別的地方看看,但直接拒絕肯定很讓人心裡不舒服,我得在想幾種拒絕策略。 。 。 ,我看我的線程池用的人還比較多,這麼多人用,要是有人一直佔著我的線程池怎麼辦,肯定得想個辦法處理?要不然就直接一個執行緒只能使用1分鐘,使用完後立刻回收,如果想在使用,重新排隊等待。這樣我的線程生意越做越好,只要有人用,他就一直跑。

是不是有點像飯店或是自助餐店,自助餐店是比較形象的,我的飯店裡面只要有位置就可以坐人,達到最大的量,剩下的客戶只能在門口等待了,飯店裡面的客戶走一個,來一個在外邊等待的,如果等待的位置沒有了,客戶看看沒位置了就直接走了,如果有的人特別想吃,就在哪多等一會兒。在飯店裡面的客戶吃的時間也不能太長(一般在沒有位置的情況下),大概2個小時,吃完就要離開。

根據以上我的描述,大概可以確定線程池裡面有什麼?

裝了多少個執行緒、能裝多少執行緒、執行緒可以保留多久、執行緒等待區、如何拒絕、建立執行緒

Java執行緒池的原理、使用以及效能最佳化方法有哪些?

1.1、為什麼要使用執行緒 

程式的運作必須依賴進程,而進程的實際執行單元就是執行緒。

  • 系統內服務的呼叫。系統是依託於進程運行的,系統內有很多服務,服務之間有交互,服務的運行依託於線程運行。多服務運行依託於多執行緒運行。服務之間的呼叫及資料交換是依託於進程間的記憶體進行資料互動的,同時執行緒也可以建構自己的記憶體空間。依託於進程間的資源調度與資料互動。

  • 多執行緒可以提高程式的執行效能。例如,有個 90 平方的房子,一個人打掃需要花費 30 分鐘,三個人打掃就只需要 10 分鐘,這三個人就是程式中的「多線程」。

在許多程式中,需要多個執行緒互相同步或互斥的並行完成工作。

線程相比進程來說,更加的輕量級,所以線程的創建和銷毀的代價變得更小。

線程提高了效能,雖然線程宏觀上是並行的,但微觀上卻是串行。從CPU角度執行緒並無法提升效能,但如果某些執行緒涉及到等待資源(例如IO,等待輸入)時,多執行緒允許進程中的其它執行緒繼續執行而不是整個行程被阻塞,因此提高了CPU的利用率,從這個角度會提升效能。

在多CPU或多核心的情況下,使用執行緒不僅在宏觀上並行,在微觀上也是並行的。

1.2、為什麼要使用線程池

多線程可以提高程式的執行性能

  • 比如說吃自助餐,當餐位足夠多的時候,人也夠多的時候,自助餐獲利也是最多了,同時也可以提供用餐率與顧客滿意度。如果有200個人吃飯,有一百個餐位的話,每個人平均吃1小時,那200個人吃兩個小時就吃完了。如果只有10個餐位的話,200個人需要吃20個小時左右,想一下如果剩下的在哪裡焦急等待吃飯的客戶心裡多麼的不開心。

  • 自助餐餐的餐位就是線程,當線程足夠多的時候就可以滿足更多的人吃飯,但是也不是說線程越多越好,畢竟不是一定每次都會有200個客戶來吃飯,就算有200個客戶過來吃飯,也需要評估一下,飯店裡面的廚師夠不夠,打掃衛生的阿姨能不能收拾過來,飯店裡面的盤子夠不夠等基本的硬體因素。這個就相當於系統的配置一下,需要一些本質上的記憶體、CPU處理等一些硬體條件。

創建/銷毀線程伴隨著系統開銷,過於頻繁的創建/銷毀線程,會很大程度上影響處理效率(只要線程一直執行就不會銷毀)

  • 記建立執行緒消耗時間T1,執行任務消耗時間T2,銷毀執行緒消耗時間T3,如果T1 T3>T2,那麼是不是說開啟一個執行緒來執行這個任務太不划算了!正好,線程池緩存線程,可用已有的閒置線程來執行新任務,避免了T1 T3帶來的系統開銷,當然一直存活的核心線程也會消耗CPU資源

線程並發數量過多,搶佔系統資源從而導致阻塞

  • 我們知道線程能共享系統資源,如果同時執行的線程過多,就有可能導致系統資源不足而產生阻塞的情況,運用執行緒池能有效的控制執行緒最大並發數,避免以上的問題

#對執行緒進行一些簡單的管理

  • 例如:延時執行、定時循環執行的策略等運用執行緒池都能進行很好的實作

1.3、執行緒池的優點

提高執行緒利用率

  • 保證存在業務是的時候使用,不存在業務的時候就釋放掉,合理使用線程,避免資源浪費

提高程式的回應速度

  • 由執行緒池統一管理的話,資源分配使用統一的調度池進行調度,出現使用執行緒的情況能避免執行緒的創建及銷毀的耗時,可以直接使用線程。

方便統一管理執行緒物件

  • 執行緒池可以保證執行緒的統一調配與管理。

可以控制最大並發數

  • 伺服器是有執行緒使用上限的,執行緒使用對資源也有很大的消耗,所以執行緒池能很好的控制線程資源,避免浪費。

2、執行緒池在java中的使用

ThreadPoolExecutor這個類別是java中的執行緒池類,可以使用它來進行執行緒的池化。

// 根据上面的描述大概分析一下线程都需要什么及参数的解析
// corePoolSize 核心线程数,就是上面说的装了多少个线程
// maximumPoolSize 最大线程数,就是上面说的能装多少线程
// keepAliveTime 存活时间,就是上面说的线程可以保留多长时间
// TimeUnit 这个是时间单位,有时、分、秒、天等等,是存活时间的单位
// BlockingQueue<Runnable> 这是一个等待队列,就是上面显示的线程等待区
// ThreadFactory 线程工厂,就是上面描述的如何创建线程,由谁创建
// RejectedExecutionHandler 拒绝策略,就是上面显示的如何拒绝,是直接拒绝还是婉拒
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

Java執行緒池的原理、使用以及效能最佳化方法有哪些?

可以看到,其需要以下幾個參數:

  • corePoolSize(必要):核心執行緒數。預設情況下,核心執行緒會一直存活,但是當將 allowCoreThreadTimeout 設為 true 時,核心執行緒也會逾時回收。

  • maximumPoolSize(必要):執行緒池所能容納的最大執行緒數。當活躍執行緒數達到該數值後,後續的新任務將會阻塞。

  • keepAliveTime(必要):執行緒閒置逾時時長。如果超過該時長,非核心執行緒就會被回收。如果將 allowCoreThreadTimeout 設為 true 時,核心執行緒也會逾時回收。

  • unit(必要):指定 keepAliveTime 參數的時間單位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分鐘)。

  • workQueue(必要):任務佇列。透過執行緒池的 execute() 方法提交的 Runnable 物件將儲存在該參數中。其採用阻塞隊列實作。

  • threadFactory(可選):線程工廠。用於指定為執行緒池建立新執行緒的方式。

  • handler(可選):拒絕策略。當達到最大執行緒數時需要執行的飽和策略。

2.1、執行緒池的工作原理

Java執行緒池的原理、使用以及效能最佳化方法有哪些?

#2.2、執行緒池的java程式碼範例

import java.util.concurrent.*;
public class ThreadTest {
    public static void main(String[] args) {
        ExecutorService threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory());
        for (int i = 0; i < 20; i++) {
            int finalI = i;
            threadPoolExecutor.submit( ()->{
                System.out.println(Thread.currentThread().getName() + "========" + finalI);
            });
        }
        threadPoolExecutor.shutdown();
    }
}

執行結果:

pool-1-thread-1========0
pool-1-thread-3========2
pool-1-thread- 3========4
pool-1-thread-2========1
pool-1-thread-3========5
pool-1-thread-2========8
pool-1-thread-5========7
pool-1-thread-1==== ====3
pool-1-thread-4========6
執行緒「主」中的例外java.util.concurrent.RejectedExecutionException:任務java.util.concurrent.FutureTask @ 61e717c2 被java.util.concurrent.ThreadPoolExecutor@66cd51c3 拒絕[正在運行,池大小= 5,活動線程= 2,排隊任務= 0,已完成任務= 7]
at java.util.concurrent.ThreadPoolExecutor$ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2063)
在java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
在java.util.concurrent.ThoolExute(
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.halo.communication.ThreadTest.main(ThreadTest.java:10)

#執行的執行緒數超過了執行緒池可承載的大小,執行緒池使用預設拒絕策略拒絕多餘執行緒執行,然後開始出現異常處理。上面執行的執行緒數到thread-5,5是執行緒池的預設最大執行緒數。然後執行循環20次,執行到8的時候出現異常,表示執行緒池已經超載負擔負載,所以執行緒池執行拒絕策略。

以上是Java執行緒池的原理、使用以及效能最佳化方法有哪些?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除