天天看點

Java多線程:線程池

系統啟動一個新線程的成本是比較高的,因為它涉及到與作業系統的互動。在這種情況下,使用線程池可以很好的提供性能,尤其是當程式中需要建立大量生存期很短暫的線程時,更應該考慮使用線程池。

與資料庫連接配接池類似的是,線程池在系統啟動時即建立大量空閑的線程,程式将一個Runnable對象傳給線程池,線程池就會啟動一條線程來執行該對象的run方法,當run方法執行結束後,該線程并不會死亡,而是再次傳回線程池中成為空閑狀态,等待執行下一個Runnable對象的run方法。

除此之外,使用線程池可以有效地控制系統中并發線程的數量,但系統中包含大量并發線程時,會導緻系統性能劇烈下降,甚至導緻JVM崩潰。而線程池的最大線程數參數可以控制系統中并發的線程不超過此數目。

在JDK1.5之前,開發者必須手動的實作自己的線程池,從JDK1.5之後,Java内建支援線程池。

與多線程并發的所有支援的類都在java.lang.concurrent包中。我們可以使用裡面的類更加的控制多線程的執行。

一、Executors類

JDK1.5中提供Executors工廠類來産生連接配接池,該工廠類中包含如下的幾個靜态工程方法來建立連接配接池:

1、public static ExecutorService newFixedThreadPool(int nThreads):建立一個可重用的、具有固定線程數的線程池。

2、public static ExecutorService newSingleThreadExecutor():建立一個隻有單線程的線程池,它相當于newFixedThreadPool方法是傳入的參數為1

3、public static ExecutorService newCachedThreadPool():建立一個具有緩存功能的線程池,系統根據需要建立線程,這些線程将會被緩存線上程池中。

4、public static ScheduledExecutorService newSingleThreadScheduledExecutor:建立隻有一條線程的線程池,他可以在指定延遲後執行線程任務

5、public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):建立具有指定線程數的線程池,它可以再指定延遲後執行線程任務,corePoolSize指池中所儲存的線程數,即使線程是空閑的也被儲存線上程池内。

上面的幾個方法都有一個重載的方法,多傳入一個ThreadFactory參數的重載方法,使用的比較少。

二、ExecutorService類

可以看到上面的5個方法中,前面3個方法的傳回值都是一個ExecutorService對象。該ExecutorService對象就代表着一個盡快執行線程的線程池(隻要線程池中有空閑線程立即執行線程任務),程式隻要将一個Runnable對象或Callable對象送出給該線程池即可,該線程就會盡快的執行該任務。

ExecutorService有幾個重要的方法:

方法摘要

<code> boolean</code>

<code>isShutdown()</code>

          如果此執行程式已關閉,則傳回 true。

<code>isTerminated()</code>

          如果關閉後所有任務都已完成,則傳回 true。

<code> void</code>

<code>shutdown()</code>

          啟動一次順序關閉,執行以前送出的任務,但不接受新任務。

<code> List&lt;Runnable&gt;</code>

<code>shutdownNow()</code>

          試圖停止所有正在執行的活動任務,暫停處理正在等待的任務,并傳回等待執行的任務清單。

<code></code>

<code>&lt;T&gt; Future&lt;T&gt;</code>

<code>submit(Callable&lt;T&gt; task)</code>

          送出一個傳回值的任務用于執行,傳回一個表示任務的未決結果的 Future。

<code> Future&lt;?&gt;</code>

<code>submit(Runnable task)</code>

          送出一個 Runnable 任務用于執行,并傳回一個表示該任務的 Future。

<code>submit(Runnable task, T result)</code>

更詳細的參考JDK API文檔。

submit方法是對 Executor接口execute方法的更好的封裝,建議使用submit方法。

三、ScheduleExecutorService類

在上面的5個方法中,後面2個方法的傳回值都是一個ScheduleExecutorService對象。ScheduleExecutorService代表可在指定延遲或周期性執行線程任務的線程池。

ScheduleExecutorService類是ExecutorService類的子類。是以,它裡面也有直接送出任務的submit方法,并且新增了一些延遲任務處理的方法:

<code>&lt;V&gt; ScheduledFuture&lt;V&gt;</code>

<code>schedule(Callable&lt;V&gt; callable, long delay, TimeUnit unit)</code>

          建立并執行在給定延遲後啟用的 ScheduledFuture。

<code> ScheduledFuture&lt;?&gt;</code>

<code>schedule(Runnable command, long delay, TimeUnit unit)</code>

          建立并執行在給定延遲後啟用的一次性操作。

<code>scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)</code>

          建立并執行一個在給定初始延遲後首次啟用的定期操作,後續操作具有給定的周期;也就是将在 initialDelay 後開始執行,然後在 initialDelay+period 後執行,接着在 initialDelay + 2 * period 後執行,依此類推。

<code>scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)</code>

          建立并執行一個在給定初始延遲後首次啟用的定期操作,随後,在每一次執行終止和下一次執行開始之間都存在給定的延遲。

下面看看線程池的簡單使用:

1、固定大小的線程池:

[java]

view plain

copy

print

?

package com.tao.test;  

import java.util.concurrent.ExecutorService;  

import java.util.concurrent.Executors;  

public class PoolTest {  

    public static void main(String[] args) {  

        ExecutorService pool=Executors.newFixedThreadPool(5);//建立一個固定大小為5的線程池   

        for(int i=0;i&lt;7;i++){  

            pool.submit(new MyThread());  

        }  

        pool.shutdown();  

    }  

}  

class MyThread extends Thread{  

    @Override  

    public void run() {  

            System.out.println(Thread.currentThread().getName()+"正在執行。。。");  

輸出結果:

pool-1-thread-1正在執行。。。  

pool-1-thread-3正在執行。。。  

pool-1-thread-2正在執行。。。  

pool-1-thread-4正在執行。。。  

pool-1-thread-5正在執行。。。  

可以看到雖然我們呢建立了7個MyThread線程對象,但是由于受線程池的大小限制,隻是開啟了5個線程,這樣就減少了并發線程的數量。

2、單任務線程池:

        ExecutorService pool=Executors.newSingleThreadExecutor();//建立一個單線程池   

可以看到,線程池隻開啟了一個線程。

3、建立可變尺寸的線程池

        ExecutorService pool=Executors.newCachedThreadPool();  

        for(int i=0;i&lt;5;i++){  

看輸出結果:

可以看到,我們沒有限制線程池的大小,但是它會根據需求而建立線程。

4、延遲線程池

        ScheduledExecutorService pool=Executors.newScheduledThreadPool(6);  

        for(int i=0;i&lt;4;i++){  

        pool.schedule(new MyThread(), 1000, TimeUnit.MILLISECONDS);  

pool-1-thread-6正在執行。。。  

可以明顯看到,最後兩個線程不是立即執行,而是延遲了1秒在執行的。

5、單任務延遲線程池

        ScheduledExecutorService pool=Executors.newSingleThreadScheduledExecutor();  

上面我們使用的是JDK幫我封裝好的線程池,我們也可以自己定義線程池,檢視源碼,我們發現,Excutors裡面的獲得線程的靜态方法,内部都是調用ThreadPoolExecutor的構造方法。比如:

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {  

    return new ThreadPoolExecutor(nThreads, nThreads,  

                                  0L, TimeUnit.MILLISECONDS,  

                                  new LinkedBlockingQueue&lt;Runnable&gt;(),  

                                  threadFactory);  

可以看到,它是通過調用ThreadPoolExecutor的構造方法來傳回一個線程池的。是以,我們也可以自己手動的調用ThreadPoolExecutor的各種構造方法,來定義自己的線程池規則,不過一般情況下,使用自帶的線程池就夠了,不需要自己來實作。