Java線程池

MelLoton 8年前發布 | 8K 次閱讀 線程池 Java Java開發

簡述

在面向軟件編程中,創建和銷毀對象是一件非常耗時的事情,因為創建一個對象要獲取內存資源或者其它更多的資源。在Java中更是如此,虛擬機將試圖跟蹤每一個對象,以便能在對象銷毀時進行回收。所以提供程序效率的方法就是減少對象的創建和銷毀。如何利用已有的對象來服務就是一個需要解決的問題。

Java線程池實現了一個Java高并發的、Java多線程的、可管理的統一調度器。java.util.concurrent.Executors工作中最常用的和熟知的,順便一提,java.util.concurrent包由出自著名的大神Doug Lea之手。

Executors是個線程工廠類,方便快速地創建很多線程池,也可以說是一個線程池工具類。配置一個線程池是比較復雜的,尤其是對于線程池原理不是很清楚的情況下,很可能配置的線程池不是最優的,以至于達不到預期的效果,因此Executors類里面給我們提供了一些靜態工廠方法,以便我們能生成常用的線程池。

常用的方法有如下三種:

  • 1、newSingleThreadExecutor:創建一個單線程的線程池。
  • 2、newFixedThreadPool:創建固定大小的線程池。
  • 3、newCachedThreadPool:創建一個可緩存的線程池。

1、newSingleThreadExecutor的使用

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

public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        ExecutorService executorService=Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int no=i;
            Runnable task=new Runnable() {

            @Override
            public void run() {
                try{
                    System.out.println("into "+no);
                    Thread.sleep(1000L);
                    System.out.println("end "+no);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        //交由線程池處理任務
        executorService.execute(task);
    }
    executorService.shutdown();
    System.out.println("main thread have terminate");
}    

}</code></pre>

運行結果如下所示:

newSingleThreadExecutor運行結果

從截圖中可以得知任務是一條一條的執行的。

查看一下Executors.newSingleThreadExecutor()的實現方法:

/**

 * Creates an Executor that uses a single worker thread operating
 * off an unbounded queue. (Note however that if this single
 * thread terminates due to a failure during execution prior to
 * shutdown, a new one will take its place if needed to execute
 * subsequent tasks.)  Tasks are guaranteed to execute
 * sequentially, and no more than one task will be active at any
 * given time. Unlike the otherwise equivalent
 * <tt>newFixedThreadPool(1)</tt> the returned executor is
 * guaranteed not to be reconfigurable to use additional threads.
 *
 * @return the newly created single-threaded Executor
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}</code></pre> 

通過源碼得知,它是調用了ThreadPoolExecutor創建了一個LinkedBlockingQueue的一個大小的線程池,采用默認的異常策略。

2、newCachedThreadPool的使用

創建一個緩沖池大小可根據需要伸縮的線程池,但是在以前構造的線程可用時將重用它們。對于執行很多短期異步任務而言,這些線程池通常可提供程序性能。調用execute將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程并添加到池中。終止并從緩存中移除那些已有60s未被使用的線程。因此,長時間保持空閑的線程池不會使用任何資源。

Demo:

public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService=Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            final int no=i;
            Runnable task=new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("into "+no);
                        Thread.sleep(10001L);
                        System.out.println("end "+no);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(task);
        }
        System.out.println("main thread have terminate");
        executorService.shutdown();
    }
}

newCachedThreadPool運行結果

從運行結果可知,任務一開始所有的線程就開始執行了,都在互相爭奪CPU資源。

接下來我們看下它的源碼實現:

/**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to <tt>execute</tt> will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

代碼創建了一個內核線程池。線程為零,來一個線程就在線程池里面創建一個SynchronousQueue。

3、newFixedThreadPool的使用

創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。在任意點,在大多數nThreads線程會處于處理任務的活動狀態。如果在所有線程處于活動狀態時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待。如果在關閉前的執行期間由于失敗而導致任何線程終止,那么一個新的線程將代替它執行后續任務(如果需要)。在某個線程被顯示關閉之前,池中的線程將一直存在。

Demo:

public class newFixedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService=Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            final int no=i;
            Runnable task=new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("into "+no);
                        Thread.sleep(1000L);
                        System.out.println("end "+no);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(task);
        }
        System.out.println("main thread have terminate");
        executorService.shutdown();
    }
}

運行結果如下:

newFixedThreadPool運行結果

從結果上面看,一下子只有5個線程同時執行,然后結束一個再執行一個。

接下來看下源碼實現:

/**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * <tt>nThreads</tt> threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());

代碼創建了一個指定大小的LinkedBlockingQueue的線程池。

線程池的好處

1、合理利用線程能帶來4個好處

(1). 降低資源消耗。通過重復利用已創建的線程,降低線程創建和銷毀造成的消耗。

(2). 提供響應速度。當任務到達時,任務可以不需要等待線程創建就能立即執行。

(3).提供線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是要做到合理地利用線程,必須對其原理了如指掌。

(4).防止服務器過載,形成內存溢出,或者CPU耗盡。

2、線程池技術如何高服務器程序的性能

這里所提及的服務器程序是指能夠接收客戶端請求并處理請求的程序,而不只是那些接受網絡客戶端請求的網絡服務器程序。多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。但如果對多線程應用不當,會增加對單個任務的處理時間。

3、舉個栗子

假設在一臺服務器完成一項任務的時間為T,并假設:

  • T1,創建線程的時間。
  • T2,在線程中執行任務所花費的時間,包括線程同步所需時間
  • T3,線程銷毀時間

    顯然T=T1+T2+T3。這是一個極度簡化的假設栗子。

    可以看出T1、T3是多線程本身帶來的開銷,我們渴望減少T1、T3所用的時間從而減少T的時間。但一些線程的使用者并沒有注意到這一點,所以在程序中頻繁的創建和銷毀線程,這導致T1和T3在T中占有相當比例。顯然這是突出了線程的弱點(T1、T3),而不是優點(并發性)。

    線程池技術正是關注如何縮短調整T1、T3時間的技術,從而提高服務器程序性能的。它把T1、T3分別安排在服務器程序啟動或結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶端請求時,不會有T1、T3的開銷了。

線程池不僅調整了T1、T3產生的時間段,而且它還顯著減少了創建線程的數目。再看一個栗子:

假設一個服務器一天要處理50000個請求,并且每個請求需要一個單獨的線程數目完成。我們比較一下利用線程技術和不利用線程池技術的服務器處理這些請求時所產生的線程總數。在線程池中,線程數一般是固定的,所以產生線程的總數不會超過線程池中的數目或者上限(以下簡稱線程池尺寸),而如果服務器不利用線程池來處理這些請求則線程總數為50000.一般線程池尺寸是遠遠小于50000的。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高整體效率。

4、線程池應用范圍

(1)需要大量的線程來完成任務,且完成任務的時間比較短。Web服務器完成網頁請求這樣的任務,使用多線程池技術是非常合適的。因為單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。但對于長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因為Telnet回話時間比線程的創建時間打多了。

(2)對性能要求苛刻的應用,比如要求服務器迅速響應客戶端請求。

(3)接收突發性的大量請求,但不至于使用服務器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池的情況下,將產生大量的線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,并出現OutOfMemory的錯誤。

 

 

來自:http://www.jianshu.com/p/aa5884bcd032

 

 本文由用戶 MelLoton 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!