jdk5.0併發包線程池的實現機制

 Jdk5.0以後提供了一個全新的線程運行控制機制,其實現被封裝在java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks三個包中,實現了執行器、異步I/O、線程池、阻塞隊列、時間調度、併發控制集合等功能。

線程池是我們常用到的功能之一,顧名思義,線程池就是存放線程的緩衝池,在一般的程序設計中,爲了提高性能或處理較爲複雜耗時的操作,我們一般開多個線程。線程池的目的就是管理這些線程,控制其生命週期以及任務分配等。

在寫較深入的線程程序之前,我們一般會問,我們的任務(實現RunnableCallable接口的類對象)被提交後是如何執行的,線程是如何被啓動及終止的,線程池中的線程數是固定的嗎,是一個線程運行一個任務還是一個線程運行多個任務的呢?

下面我們結合jdk的源代碼來分析一下線程池的實現模型。

1.      生命週期

線程池的生命週期一共存在四種運行狀態標誌,分別爲RUNNINGSHUTDOWNSTOPTERMINATED,定義如下:

RUNNING:接受新任務並處理已排隊(提交的任務是被放在一隊列中)任務

SHUTDOWN:不接受新任務,但是處理已排隊任務

STOP:不接受新任務,不處理已排隊任務,並終止正在運行的任務

TERMINATED:STOP相同,並加上所有任務均已終止

下面是各個狀態之間的轉換:

RUNNING -> SHUTDOWN:調用shutdown()函數

(RUNNING or SHUTDOWN) -> STOP:調用shutdownNow()函數

SHUTDOWN -> TERMINATED:當任務隊列和線程池均爲空

STOP -> TERMINATED:當線程池爲空

這個要注意一下,在調用執行器提交完任務之後,一定要調用執行器的shutdown()或者shutdownNow()方法來終止線程池,不然的話即使所有任務處理完畢,虛擬機也不會退出。

        2.  調度過程

      無論怎麼寫程序,線程池也是有其執行器創建的,並在其構造函數中確定了線程池大小,下面是ThreadPoolExecutor的構造函數:

public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              ThreadFactory threadFactory,

                              RejectedExecutionHandler handler) {…}

corePoolSize:核心線程池的大小,即即使線程空閒,也會保持這個數目

maximumPoolSize:線程池的最大大小,一般是Integer.MAX_VALUEcorePoolSize必須小於或等於這個數

workQueue:任務被執行之前放在這個隊列中,線程要執行任務時從這個隊列中取。注意BlockingQueue已經內置了同步機制,隊列滿或空均會使其處於對待狀態。

其他參數不再說明。

通過上面大家可以看到線程和任務是分開存放管理的,這一點是理解線程池的關鍵。

下面我們通過一個函數看一下線程是如何被創建和啓動的:

 

private void delayedExecute(Runnable command) {

              //如果運行狀態爲SHUTDOWN,拒絕此命令

        if (isShutdown()) {

            reject(command);

            return;

        }

        //如果當前線程數小於核心線程數,則創建並啓動一個線程

        if (getPoolSize() < getCorePoolSize())

            prestartCoreThread();

 

              //把任務加入到任務隊列中

        super.getQueue().add(command);

}
那麼線程是如何執行任務的呢:

    總體來說,提交的任務按照提交先後順序被放入任務隊列,線程啓動後從隊列中取出任務執行,任務被取出後即被從隊列中刪除。由於操作系統的調度和每個任務的運算量不同,因此單個線程取出的任務並沒有順序,但是一定是隊列的頭,每個線程都是平等的,不允許取頭後面的任務。

             下面是具體代碼:

               

public void run() {

                     try {

                               Runnable task = firstTask;

                               firstTask = null;

                               //注意下面這個while循環,只要有任務就不會被終止,

                               //getTask()是取出任務隊列的頭,如果隊列爲空,進入阻塞狀態

                               //runTask()是運行這個任務

                               while (task != null || (task = getTask()) != null) {

                               runTask(task);

                               task = null;

                        }

                    } finally {

                      workerDone(this);

                   }

              }

 

    更多的具體細節,請大家看ThreadPoolExecutorScheduledThreadPoolExecutor類  的源代碼,兩者都位於java.util.concurrent包中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章