歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java線程池和隊列分析

Java項目 當想讓程序異步操作的時候,首先考慮使用Java多線程,但有的時候我們總會在想是簡單的extends Thread 、implements Runnable接口還是使用線程池呢?而大多開發者可能更會選擇使用線程池,.減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。

2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

最近為什麼要研究這個?首先 前一段時間 公司使用dubbo服務,同事說dubbo報了一個錯是"rejectionExecution ThreadPool exhausted", 很明顯dubbo使用固定大小的線程池,後來增加線程池大小,優化業務代碼。

Java裡面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。

比較重要的幾個類:

ExecutorService

真正的線程池接口。

ScheduledExecutorService

能和Timer/TimerTask類似,解決那些需要任務重復執行的問題。

ThreadPoolExecutor

ExecutorService的默認實現。

ScheduledThreadPoolExecutor

繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,周期性任務調度的類實現。

要配置一個線程池是比較復雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在Executors類裡面提供了一些靜態工廠,生成一些常用的線程池。

1. newSingleThreadExecutor

創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

2.newFixedThreadPool

創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。

3. newCachedThreadPool

創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,

那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。

4.newScheduledThreadPool

創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。

TestCachedThreadPool demo:

publicclass TestCachedThreadPool {

    publicstaticvoid main(String[] args) {

        //創建一個可重用固定線程數的線程池

        ExecutorService pool = Executors.newCachedThreadPool();

        //創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口

        Thread t1 = new MyThread();

        Thread t2 = new MyThread();

        //將線程放入池中進行執行

        pool.execute(t1);

        pool.execute(t2);

        //關閉線程池

        pool.shutdown();

    }

}

TestScheduledThreadPoolExecutor demo:

 

publicclass TestScheduledThreadPoolExecutor {

    publicstaticvoid main(String[] args) {

        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段時間就觸發異常

                      @Override

                      publicvoid run() {

                           System.out.println("TestScheduledThreadPoolExecutor-->>run() error");

                      }

                  }, 1000, 5000, TimeUnit.MILLISECONDS);

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段時間打印系統時間,證明兩者是互不影響的

                      @Override

                      publicvoid run() {

                           System.out.println(System.nanoTime());

                      }

                  }, 1000, 2000, TimeUnit.MILLISECONDS);

    }

}

 

corePoolSize - 池中所保存的線程數,包括空閒線程。
maximumPoolSize-池中允許的最大線程數。
keepAliveTime - 當線程數大於核心時,此為終止前多余的空閒線程等待新任務的最長時間。
unit - keepAliveTime 參數的時間單位。
workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute方法提交的 Runnable任務。
threadFactory - 執行程序創建新線程時使用的工廠。
handler - 由於超出線程范圍和隊列容量而使執行被阻塞時所使用的處理程序。
ThreadPoolExecutor是Executors類的底層實現。

固定線程池大小: 

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

單線程: 

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

無界線程池,可以進行自動線程回收: 

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

首先是無界的線程池,所以我們可以發現maximumPoolSize為65535。其次BlockingQueue的選擇上使用SynchronousQueue。可能對於該BlockingQueue有些陌生,簡單說:該QUEUE中,每個插入操作必須等待另一個線程的對應移除操作。 

先從BlockingQueue<Runnable> workQueue這個入參開始說起。在JDK中,其實已經說得很清楚了,一共有三種類型的queue。

所有BlockingQueue 都可用於傳輸和保持提交的任務。可以使用此隊列與池大小進行交互:

如果運行的線程少於 corePoolSize,則 Executor始終首選添加新的線程,而不進行排隊。(如果當前運行的線程小於corePoolSize,則任務根本不會存放,添加到queue中,而是直接抄家伙(thread)開始運行)

如果運行的線程等於或多於 corePoolSize,則 Executor始終首選將請求加入隊列,而不添加新的線程

如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。

queue上的三種類型。 

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-01/111467p2.htm

Copyright © Linux教程網 All Rights Reserved