Java線程池(2)——線程池中的幾個重要方法詳解

【內容摘要】

在java中,如果需要進行多線程編程,可以採用java自帶的線程池來實現,線程池對於我們新手來說是一個非常好的選擇,因爲我們可以不用關心線程池中線程是如何調度的,避免在多線程編程過程產生死鎖等問題。在瞭解線程池的使用前,本文首先介紹一下java線程池的內部原理。

【正文】

上一篇文檔http://blog.csdn.net/zly_ir/article/details/78785237 在介紹了線程池中的幾個重要的類過程中,提到了幾個重要的方法,如execute()方法等,在本篇文章中,我們將詳細介紹execute()方法是如何實現的,在介紹方法之前,我們首先回顧一下ThreadPoolExecutor中的幾個重要參數,這些參數將在後續方法中經常出現:

//任務緩存隊列,用來存放等待執行的任務
private final BlockingQueue<Runnable> workQueue;              

//線程池的主要狀態鎖,對線程池狀態(比如線程池大小                                                              //runState等)的改變都要使用這個鎖
private final ReentrantLock mainLock = new ReentrantLock();   

//用來存放工作集
private final HashSet<Worker> workers = new HashSet<Worker>(); 

//線程存貨時間  
private volatile long  keepAliveTime;  

//是否允許爲核心線程設置存活時間
private volatile boolean allowCoreThreadTimeOut; 

//核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列)
private volatile int   corePoolSize;    

//線程池最大能容忍的線程數
private volatile int   maximumPoolSize; 

//線程池中當前的線程數
private volatile int   poolSize; 

//任務拒絕策略
private volatile RejectedExecutionHandler handler;

//線程工廠,用來創建線程
private volatile ThreadFactory threadFactory;

//用來記錄線程池中曾經出現過的最大線程數
private int largestPoolSize; 

//用來記錄已經執行完畢的任務個數
private long completedTaskCount; 

1.execute()方法

在ThreadPoolExecutor類中,最核心的任務提交方法是execute()方法,雖然通過submit也可以提交任務,但是實際上submit方法裏面最終調用的還是execute()方法,所以我們以execute()方法的實現原理爲例進行說明:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    //如果線程池中當前線程數不小於核心池大小
    //或者addIfUnderCorePoolSize這個方法返回false
    //則進入if語句執行接下去的代碼
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        //如果當前線程池處於RUNNING狀態,則將任務放入任務緩存隊列;
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        //如果當前線程池不處於RUNNING狀態或者任務放入緩存隊列失敗
        //則執addIfUnderMaximumPoolSize(command)
        else if (!addIfUnderMaximumPoolSize(command))
            //如果執行addIfUnderMaximumPoolSize方法失敗
            //則執行reject()方法進行任務拒絕處理。
            reject(command); // is shutdown or saturated
    }
}

2.在execute()方法中,有兩個重要的功能模塊,addIfUnderCorePoolSize和addIfUnderMaximumPoolSize,下面介紹這兩個功能的具體實現,並附上註釋
(1)addIfUnderCorePoolSize

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //判斷當前線程池中的線程數目是否小於核心池大小;
        //判斷前首先獲取鎖,目的是爲了防止在在execute方法判斷的時候
        //poolSize小於corePoolSize,而判斷完之後,在其他線程中又向
        //線程池提交了任務,就可能導致poolSize不小於corePoolSize了
        if (poolSize < corePoolSize && runState == RUNNING)
            //創建線程去執行firstTask任務
            //傳進去的參數爲提交的任務,返回值爲Thread類型
            //具體實現下面會介紹   
            t = addThread(firstTask);   
        } finally {
        mainLock.unlock();
    }
    //爲空則表明創建線程失敗
    //(即poolSize>=corePoolSize或者runState不等於RUNNING)
    if (t == null)
        return false;
    t.start();
    return true;
}

在addIfUnderCorePoolSize方法中,有一個任務添加函數addThread,它的具體實現如下:

private Thread addThread(Runnable firstTask) {
    //用提交的任務創建了一個Worker對象
    Worker w = new Worker(firstTask);
    //調用線程工廠threadFactory創建了一個新的線程t
    Thread t = threadFactory.newThread(w); 
    if (t != null) {
        //將線程t的引用賦值給了Worker對象的成員變量thread
        w.thread = t;  
        //通過workers.add(w)將Worker對象添加到工作集當中
        workers.add(w);
        //當前線程數加1
        int nt = ++poolSize;       
        if (nt > largestPoolSize)
            largestPoolSize = nt;
    }
    return t;
}

addThread中的Worker類是一個實現了Runnable 的類,具體實現如下

private final class Worker implements Runnable {
    private final ReentrantLock runLock = new ReentrantLock();
    private Runnable firstTask;
    volatile long completedTasks;
    Thread thread;
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
    }
    boolean isActive() {
        return runLock.isLocked();
    }
    void interruptIfIdle() {
        final ReentrantLock runLock = this.runLock;
        if (runLock.tryLock()) {
            try {
        if (thread != Thread.currentThread())
        thread.interrupt();
            } finally {
                runLock.unlock();
            }
        }
    }
    void interruptNow() {
        thread.interrupt();
    }

    private void runTask(Runnable task) {
        final ReentrantLock runLock = this.runLock;
        runLock.lock();
        try {
            if (runState < STOP &&
                Thread.interrupted() &&
                runState >= STOP)
            boolean ran = false;
             //beforeExecute方法是ThreadPoolExecutor類的一個方法,
             //沒有具體實現,用戶可以根據自己需要重載這個方法和後面的
             //afterExecute方法進行統計信息,比如某個任務的執行時間等   
            beforeExecute(thread, task);          
            try {
                task.run();
                ran = true;
                afterExecute(task, null);
                ++completedTasks;
            } catch (RuntimeException ex) {
                if (!ran)
                    afterExecute(task, ex);
                throw ex;
            }
        } finally {
            runLock.unlock();
        }
    }

    public void run() {
        try {
            //首先執行的是通過構造器傳進來的任務firstTask
            Runnable task = firstTask;
            firstTask = null;
            //不斷通過getTask()去取新的任務來執行
            //getTask是ThreadPoolExecutor類中的方法,並不是Worker類中的方法
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);   //當任務隊列中沒有任務時,進行清理工作       
        }
    }
}

上述run()方法中的getTask是ThreadPoolExecutor類中的方法,他的具體實現如下:

Runnable getTask() {
    for (;;) {
        try {
            int state = runState;
            //判斷當前線程池狀態,如果runState大於SHUTDOWN
            //(即爲STOP或者TERMINATED),則直接返回null。
            if (state > SHUTDOWN)
                return null;
            Runnable r;
            //如果runState爲SHUTDOWN或者RUNNING,則從任務緩存隊列取任務。
            if (state == SHUTDOWN)
                r = workQueue.poll();
            //如果線程數大於核心池大小或者允許爲核心池線程設置空閒時間,
            //則通過poll取任務,若等待一定的時間取不到任務,則返回null
            else if (poolSize > corePoolSize || allowCoreThreadTimeOut) 
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
            else
                r = workQueue.take();
            if (r != null)
                return r;
            //如果沒取到任務,即r爲null,則判斷當前的worker是否可以退出  
            //workerCanExit() 方法將在之後介紹
            if (workerCanExit()) {    
                if (runState >= SHUTDOWN) 
                    interruptIdleWorkers();   //中斷處於空閒狀態的worker,該方法將在之後介紹
                return null;
            }
        } catch (InterruptedException ie) {
            // On interruption, re-check runState
        }
    }
}

在getTask()中,如果線程池處於STOP狀態、或者任務隊列已爲空或者允許爲核心池線程設置空閒存活時間並且線程數大於1時,允許worker退出。如果允許worker退出,則調用interruptIdleWorkers()中斷處於空閒狀態的worker,這裏有兩個重要函數,一個是workerCanExit(),另一個是interruptIdleWorkers(),下面分別介紹這兩個函數的具體實現

private boolean workerCanExit() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    boolean canExit;
    //如果runState大於等於STOP,或者任務緩存隊列爲空了
    //或者  允許爲核心池線程設置空閒存活時間並且線程池中的線程數目大於1
    try {
        canExit = runState >= STOP ||
            workQueue.isEmpty() ||
            (allowCoreThreadTimeOut &&
             poolSize > Math.max(1, corePoolSize));
    } finally {
        mainLock.unlock();
    }
    return canExit;
}

這裏有一個非常巧妙的設計方式,假如我們來設計線程池,可能會有一個任務分派線程,當發現有線程空閒時,就從任務緩存隊列中取一個任務交給空閒線程執行。但是在這裏,並沒有採用這樣的方式,因爲這樣會要額外地對任務分派線程進行管理,無形地會增加難度和複雜度,這裏直接讓執行完任務的線程去任務緩存隊列裏面取任務來執行。

(2)addIfUnderMaximumPoolSize,這個方法的實現思想和addIfUnderCorePoolSize方法的實現思想非常相似,唯一的區別在於addIfUnderMaximumPoolSize方法是在線程池中的線程數達到了核心池大小並且往任務隊列中添加任務失敗的情況下執行的:

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < maximumPoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

其實它和addIfUnderCorePoolSize方法的實現基本一模一樣,只是if語句判斷條件中的poolSize < maximumPoolSize不同而已。

【附錄】

線程池大小配置參考值

  • 如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設爲 NCPU+1
  • 如果是IO密集型任務,參考值可以設置爲2*NCPU

【參考文獻】

http://www.cnblogs.com/dolphin0520/p/3932921.html

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