《Java源碼分析系列》線程池 ThreadPoolExecutor

《Java源碼分析》:線程池 ThreadPoolExecutor

ThreadPoolExecutor是ExecutorService的一張實現,但是是間接實現。

ThreadPoolExecutor是繼承AbstractExecutorService。而AbstractExecutorService實現了ExecutorService接口。

在介紹細節的之前,先介紹下ThreadPoolExecutor的結構

1、線程池需要支持多個線程併發執行,因此有一個線程集合Collection來執行線程任務;

2、很顯然在多個線程之間協調多個任務,那麼就需要一個線程安全的任務集合,同時還需要支持阻塞、超時操作,那麼BlockingQueue是必不可少的;

3、既然是線程池,出發點就是提高系統性能同時降低資源消耗,那麼線程池的大小就有限制,因此需要有一個核心線程池大小(線程個數)和一個最大線程池大小(線程個數),有一個計數用來描述當前線程池大小;

4、如果是有限的線程池大小,那麼長時間不使用的線程資源就應該銷燬掉,這樣就需要一個線程空閒時間的計數來描述線程何時被銷燬;

5、前面描述過線程池也是有生命週期的,因此需要有一個狀態來描述線程池當前的運行狀態;

6、線程池的任務隊列如果有邊界,那麼就需要有一個任務拒絕策略來處理過多的任務,同時在線程池的銷燬階段也需要有一個任務拒絕策略來處理新加入的任務;

7、上面種的線程池大小、線程空閒實際那、線程池運行狀態等等狀態改變都不是線程安全的,因此需要有一個全局的鎖(mainLock)來協調這些競爭資源;

8、除了以上數據結構以外,ThreadPoolExecutor還有一些狀態用來描述線程池的運行計數,例如線程池運行的任務數、曾經達到的最大線程數,主要用於調試和性能分析。

構造函數

    public ThreadPoolExecutor(int corePoolSize,  //核心線程容量
                              int maximumPoolSize,//線程池的允許線程的最大容量
                              long keepAliveTime,//存活時間
                              TimeUnit unit,//時間單位
                              BlockingQueue<Runnable> workQueue,//任務隊列,默認Executors.defaultThreadFactory()
                              ThreadFactory threadFactory) {//線程工程,
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);//
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在構造函數中,裏面有一個線程工廠ThreadFactory,這裏需要介紹下。

ThreadFactory的介紹

既然是線程池,先看下線程是如何被創建出來的。

    public interface ThreadFactory {

        /*
         * Constructs a new {@code Thread}.  Implementations may also initialize
         * priority, name, daemon status, {@code ThreadGroup}, etc.
         */
        Thread newThread(Runnable r);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Thread接口中只有一個newThread方法用於產生一個線程。其默認實現類爲DefaultThreadFactory類,此類是在Executors類的內部類。

    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);//線程池標號
        private final ThreadGroup group;//線程組
        private final AtomicInteger threadNumber = new AtomicInteger(1);//線程標號
        private final String namePrefix;//

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);//所有線程都默認設置爲非Daemon線程
            //產生的所有線程優先級相同
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

從上面代碼中可以得到以下幾點信息:

1、在這個線程工廠中,同一個線程池的所有線程屬於同一個線程組,也就是創建線程池的那個線程組, 
同時線程池的名稱都是“pool--thread-”, 
其中poolNum是線程池的數量序號,threadNum是此線程池中的線程數量序號。

2、另外對於線程池中的所有線程默認都轉換爲非後臺線程,這樣主線程退出時不會直接退出JVM,而是等待線程池結束。

3、還有一點就是默認將線程池中的所有線程都調爲同一個級別,這樣在操作系統角度來看所有系統都是公平的,不會導致競爭堆積。

executor方法

當我們使用ThreadPoolExecutor的構造方法構造出一個線程池後,我們將調用executor方法來執行任務。下面我們就來分析下ThreadPoolExecutor中executor的內部實現。

在executor方法上面有這樣一段說明,如下:

      Executes the given task sometime in the future.  The task
      may execute in a new thread or in an existing pooled thread.

      If the task cannot be submitted for execution, either because this
      executor has been shutdown or because its capacity has been reached,
      the task is handled by the current {@code RejectedExecutionHandler}.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面的意思比較好理解,我們可以從中得到如下的信息

1、任務將在未來的某個時刻可能被執行,意味着可能不被執行,爲什麼又這個可能呢?下面再說。

2、任務可能在一個新線程中也可能在線程池中已存在的線程中執行

3、如果任務不能被提交執行,可能是由於線程池已經shutdowm或者是任務隊列中已經滿了。這種情況下任務將由此時的任務拒絕策略決定怎麼來處理

這裏來回答爲什麼會在未來的某個時刻被執行,意味着可能不被執行?

如果不被執行,可能是出現了上面所說的第3中的情況(shutdown或者是任務隊列滿了),在未來被執行即延時執行可能因爲需要等待線程來處理。

下面爲executor方法的代碼

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *翻譯:如果目前工作的線程小於核心線程的個數,則爲新任務嘗試的開啓新的線程來處理。
         *且調用addWorker來自動檢測runState和workerCount.防止本來不需要添加線程而要求添加線程的假警報
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        /*
        如果此時的線程數小於核心線程數,
        則嘗試的爲任務command開啓一個線程
        */
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //檢測線程池是否處於工作狀態且任務成功添加到阻塞隊列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            /*
                重新檢測一遍,如果線程池沒有工作,
            則考慮將任務從隊列中移除,如果移除成功則根據任務拒絕策略類進行處理
            */
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)//如果工作線程數爲0,則
                addWorker(null, false);
        }
        else if (!addWorker(command, false))//如果我們添加到隊列失敗,則嘗試開啓一個新的線程
            reject(command);
    }

    //判斷線程池是否處於Running狀態
    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

executor源碼中的註釋已經很明確的說明了exector方法的流程。這裏我再梳理如下:

線程池的工作流程時這樣的:

1、當我們提交一個任務,檢測線程池中的工作線程是否小於核心線程數,如果小於,則進行2,否則進行3.

2、嘗試建一個新的線程來將任務作爲第一個任務來執行,如果建立成功,則立即返回。否則進行3

3、檢測線程池是否處於工作狀態,如果正在工作,則進行4,否則進行10.

4、將任務加入到任務隊列中,如果加入成功,則進行5.否則,進行10

5、重新檢測線程池是否處於工作狀態,如果不是,則進行6,如果是,則進行7

6、將任務從任務隊列中移除,如果移除成功,則利用任務拒絕策略來處理該任務。否則進行7.

7、檢查工作線程數是否爲零。如果不爲零,則直接退出。否則進行8.

8、添加一個新的線程且該線程的第一個任務爲null。無論是否成功均退出。

10、嘗試建立一個新的線程來處理該任務,如果建立失敗則利用任務拒絕策略來進行處理。

比較複雜哈,好好理解下,應該沒什麼問題。

上面的方法中調用了addWorker來添加新的線程來處理新的任務。下面我們就來看下這個方法的內部實現。

addWorker方法

此函數的功能:

根據當前線程池的狀態和給的邊界條件來檢測是否需要一個新的線程添加。 
如果需要,則添加到線程隊列中並調整工作線程數並啓動線程執行第一個任務。 
如果該方法檢測到線程池處於STOP狀態或者是察覺到將要停止,則返回false。 
如果線程工廠創建線程失敗(可能是由於發生了OOM異常)則也返回false。

按照上面的描述來看addWorker具體代碼的實現就相當好理解了。哈,已有一定的註釋,這裏就不再過多的介紹了。

    private boolean addWorker(Runnable firstTask, boolean core) {
        //先是各種檢測來判斷是否需要建立一個新的Worker對象
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))//大於邊界就不創建
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;//退出循環,即需要建立一個新的Worker對象
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        //開始產生一個Worker對象,並將其添加到線程隊列中去

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);//添加到線程隊列中
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {//添加成功,則該啓動線程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)//如果沒有啓動成功,執行addWorkerFailed方法
                addWorkerFailed(w);//功能:從線程隊列中將其移除。
        }
        return workerStarted;
    }

    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

最後我們就來看下線程類Worker。

線程類Worker

Worker類主要維持線程執行任務時的中斷控制狀態,以及其他的一些小的記錄。 
Worker類繼承了AQS同步器,用於在執行任務前後來獲取和釋放鎖。這可防止中斷,在等待一個任務時 
旨在喚醒工作線程而不是中斷正在運行的任務。這裏實現了一個簡單的非重入獨佔鎖而不是使用ReentrantLock。 
主要是因爲不想工作任務正在執行線程池中的控制方法時能夠被再次鎖住。 
除此之外,爲了靜止中斷知道線程正在開始執行任務,我們將 
鎖的初始狀態設置爲一個負數,當啓動時將在runWorker中清除。

源代碼如下:

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);//利用線程工廠產生一個線程,可能失敗
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);//調用runWorker方法來執行這個任務
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

以上就是關於Worker類的介紹。

當Worker類中的線程啓動之後,則會調用Worker類中的run方法,而run方法調用了runWorker 方法,因此,下面我們就看下runWorker方法

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);//在ThreadPoolExecutor是空實現
                    Throwable thrown = null;
                    try {
                        task.run();//重點
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);//在ThreadPoolExecutor是空實現
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

上面方法拋出各種檢查,簡單來說就是:線程池正常工作的時候直接調用了我們自己提交的任務的run方法。上面代碼的重點就是這句話: task.run();

下面我們配合一個例子來總結下使用線程池來執行任務的總流程

    public class ExecutorDemo {

        private static  SimpleDateFormat sdf  = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        public static void main(String[] args) {
            int corePoolSize = 1;
            int maximumPoolSize = 1;
            BlockingQueue queue = new  ArrayBlockingQueue<Runnable>(1);
            ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize,  maximumPoolSize, 
                    0, TimeUnit.SECONDS, queue ) ;
            pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            for(int i=0;i<10;i++){
                final int index = i;
                pool.submit(new Runnable(){

                    @Override
                    public void run() {
                        log(Thread.currentThread().getName()+"begin run task :"+index);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        log(Thread.currentThread().getName()+" finish run  task :"+index);
                    }

                });
            }

            log("main thread before sleep!!!");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log("before shutdown()");

            pool.shutdown();

            log("after shutdown(),pool.isTerminated=" + pool.isTerminated());
            try {
                pool.awaitTermination(1000L, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log("now,pool.isTerminated=" + pool.isTerminated());
        }

        protected static void log(String string) {
            System.out.println(sdf.format(new Date())+"  "+string);
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

1、第一步,當我們在程序中使用submit方法提交一個任務的時候,如果需要創建一個新的Worker(線程)來執行時,就調用線程工廠創建一個線程並和任務一起組合成爲一個Worker對象。,同時將Worker對象添加到Worker工作隊列中。需要說明的是,Worker隊列構造的時候帶着一個任務Runnable,因此Worker創建時總是綁定着一個待執行任務。

換句話說,創建線程的前提是有必要創建線程(任務數已經超出了線程或者強制創建新的線程,具體見前面的源碼分析),不會無緣無故創建一堆空閒線程等着任務。這是節省資源的一種方式。

2、一旦線程池啓動剛剛創建的Worker對象中包含的線程後,就會調用Worker對象中的run方法,那麼線程工作隊列Worker就從第1個任務開始執行,(這時候發現構造Worker時傳遞一個任務的好處了),一旦第1個任務執行完畢,就從線程池的任務隊列中取出下一個任務進行執行。循環如此,直到線程池被關閉或者任務拋出了一個RuntimeException。

由此可見,線程池的基本原理其實也很簡單,無非預先啓動一些線程,線程進入死循環狀態,每次從任務隊列中獲取一個任務進行執行,直到線程池被關閉。如果某個線程因爲執行某個任務發生異常而終止,那麼重新創建一個新的線程而已。如此反覆。

其實,線程池原理看起來簡單,但是複雜的是各種策略,例如何時該啓動一個線程, 
何時該終止、掛起、喚醒一個線程,任務隊列的阻塞與超時,線程池的生命週期以及任務拒絕策略等等。

小結

在看參考博客的時候,對線程池的理解還是懵懂懵懂的,寫完這篇博客之後,發現對線程池的理解又多了一些。

線程池的工作原理:當我們寫程序中使用submit往線程池中提交新任務的時候,線程池首先會判斷是否需要生成新的線程來執行這個任務。如果需要,則新建一個,然後啓動此線程,接着就是一個一個的任務被啓動的線程執行了。以上。

參考資料

1、http://www.blogjava.net/xylz/archive/2011/01/18/343183.html

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