Java中的線程池從精通到入門

說真的,我一直認爲線程池很簡單,也沒去看過它的實現,大概瞭解過其中的原理,但是並未深入學習。一方面,瞭解過之後很長時間不去看,非常容易忘;另一方面,還是深入源碼得到的信息纔會比較深刻,還能避免背書式學習。

繼承結構說明

Executors中,有幾個靜態方法,預設了幾個ThreadPoolExecutor,其實主要的區別在於new一個實例的時候,傳入的參數不一樣。關於這幾個預設的ThreadPoolExecutor可以在瞭解其參數詳細的定義後,再進行分析。

用法簡介 - 註釋

ThreadPoolExecutor的類前註釋,當閱讀理解隨便看看,看個大概,都比網上的文章要優質。

An ExecutorService that executes each submitted task using one of possibly several pooled threads, normally configured using Executors factory methods.

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks. Each ThreadPoolExecutor also maintains some basic statistics, such as the number of completed tasks.

To be useful across a wide range of contexts, this class provides many adjustable parameters and extensibility hooks. However, programmers are urged to use the more convenient Executors factory methods Executors.newCachedThreadPool (unbounded thread pool, with automatic thread reclamation), Executors.newFixedThreadPool (fixed size thread pool) and Executors.newSingleThreadExecutor (single background thread), that preconfigure settings for the most common usage scenarios. Otherwise, use the following guide when manually configuring and tuning this class:

Core and maximum pool sizes

A ThreadPoolExecutor will automatically adjust the pool size (see getPoolSize) according to the bounds set by corePoolSize (see getCorePoolSize) and maximumPoolSize (see getMaximumPoolSize). When a new task is submitted in method execute(Runnable), if fewer than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads are idle. Else if fewer than maximumPoolSize threads are running, a new thread will be created to handle the request only if the queue is full. By setting corePoolSize and maximumPoolSize the same, you create a fixed-size thread pool. By setting maximumPoolSize to an essentially unbounded value such as Integer.MAX_VALUE, you allow the pool to accommodate an arbitrary number of concurrent tasks. Most typically, core and maximum pool sizes are set only upon construction, but they may also be changed dynamically using setCorePoolSize and setMaximumPoolSize.

當一個新任務通過execute()方法執行時,如果當前運行的線程數小於核心數,那麼會新開一個線程處理這個任務,即使別的線程在閒置;如果當前在跑的線程數大於等於核心數但小於最大線程數,若Queue沒有滿,那麼會將改任務加入Queue中,若Queue滿了,那麼會新增一個線程去處理改任務;如果大於等於最大線程數,會觸發決絕策略。

On-demand construction

By default, even core threads are initially created and started only when new tasks arrive, but this can be overridden dynamically using method prestartCoreThread or prestartAllCoreThreads. You probably want to prestart threads if you construct the pool with a non-empty queue.

可以預先初始化好線程數,不用一次次execute時慢慢增加。

Creating new threads

New threads are created using a ThreadFactory. If not otherwise specified, a Executors.defaultThreadFactory is used, that creates threads to all be in the same ThreadGroup and with the same NORM_PRIORITY priority and non-daemon status. By supplying a different ThreadFactory, you can alter the thread’s name, thread group, priority, daemon status, etc. If a ThreadFactory fails to create a thread when asked by returning null from newThread, the executor will continue, but might not be able to execute any tasks. Threads should possess the “modifyThread” RuntimePermission. If worker threads or other threads using the pool do not possess this permission, service may be degraded: configuration changes may not take effect in a timely manner, and a shutdown pool may remain in a state in which termination is possible but not completed.

創建新線程是通過ThreadFactorynewThread()方法,默認實現是Executors.defaultThreadFactory。可自定義線程的名稱、是否爲守護進程、優先級等。

Keep-alive times

If the pool currently has more than corePoolSize threads, excess threads will be terminated if they have been idle for more than the keepAliveTime (see getKeepAliveTime(TimeUnit)). This provides a means of reducing resource consumption when the pool is not being actively used. If the pool becomes more active later, new threads will be constructed. This parameter can also be changed dynamically using method setKeepAliveTime(long, TimeUnit). Using a value of Long.MAX_VALUE TimeUnit.NANOSECONDS effectively disables idle threads from ever terminating prior to shut down. By default, the keep-alive policy applies only when there are more than corePoolSize threads, but method allowCoreThreadTimeOut(boolean) can be used to apply this time-out policy to core threads as well, so long as the keepAliveTime value is non-zero.

當線程數超過核心數,如果線程閒置超過keepAliveTime ,那麼該線程將會被關閉。也可以通過 allowCoreThreadTimeOut(boolean),回收核心線程。可拓展啊,牛逼。

Queuing

Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:

  • If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
  • If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
  • If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

大於核心數,存儲到queue;如果queue滿了,新建線程,直到到達maximumPoolSize

There are three general strategies for queuing:

  • Direct handoffs. A good default choice for a work queue is a SynchronousQueue that hands off tasks to threads without otherwise holding them. Here, an attempt to queue a task will fail if no threads are immediately available to run it, so a new thread will be constructed. This policy avoids lockups when handling sets of requests that might have internal dependencies. Direct handoffs generally require unbounded maximumPoolSizes to avoid rejection of new submitted tasks. This in turn admits the possibility of unbounded thread growth when commands continue to arrive on average faster than they can be processed.
  • Unbounded queues. Using an unbounded queue (for example a LinkedBlockingQueue without a predefined capacity) will cause new tasks to wait in the queue when all corePoolSize threads are busy. Thus, no more than corePoolSize threads will ever be created. (And the value of the maximumPoolSize therefore doesn’t have any effect.) This may be appropriate when each task is completely independent of others, so tasks cannot affect each others execution; for example, in a web page server. While this style of queuing can be useful in smoothing out transient bursts of requests, it admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed.
  • Bounded queues. A bounded queue (for example, an ArrayBlockingQueue) helps prevent resource exhaustion when used with finite maximumPoolSizes, but can be more difficult to tune and control. Queue sizes and maximum pool sizes may be traded off for each other: Using large queues and small pools minimizes CPU usage, OS resources, and context-switching overhead, but can lead to artificially low throughput. If tasks frequently block (for example if they are I/O bound), a system may be able to schedule time for more threads than you otherwise allow. Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, which also decreases throughput.

隊列的三種策略。第一種,不讓存儲到到queue,一有存儲動作,就新建線程,最大線程數設置成無限制;第二種,隊列的長度無限,這樣會導致最大線程數失效;第三種,有界隊列。

Rejected tasks

New tasks submitted in method execute(Runnable) will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated. In either case, the execute method invokes the RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor) method of its RejectedExecutionHandler. Four predefined handler policies are provided:

  • In the default ThreadPoolExecutor.AbortPolicy, the handler throws a runtime RejectedExecutionException upon rejection.
  • In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes execute itself runs the task. This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted.
  • In ThreadPoolExecutor.DiscardPolicy, a task that cannot be executed is simply dropped.
  • In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not shut down, the task at the head of the work queue is dropped, and then execution is retried (which can fail again, causing this to be repeated.)

It is possible to define and use other kinds of RejectedExecutionHandler classes. Doing so requires some care especially when policies are designed to work only under particular capacity or queuing policies.

拒絕任務發生在線程池已經處於SHUT_DOWN 狀態,或最大線程數以及隊列長度有限,並處於飽和狀態時。預置的拒絕策略有4種,分別爲:第一種,直接拋異常;第二種,調用者在自己的線程中執行該任務;第三種,直接拋棄;第四種,丟棄等待時間最久的那個任務。

自己可以自定義拒絕策略。

Hook methods

This class provides protected overridable beforeExecute(Thread, Runnable) and afterExecute(Runnable, Throwable) methods that are called before and after execution of each task. These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals, gathering statistics, or adding log entries. Additionally, method terminated can be overridden to perform any special processing that needs to be done once the Executor has fully terminated.
If hook, callback, or BlockingQueue methods throw exceptions, internal worker threads may in turn fail, abruptly terminate, and possibly be replaced.

有鉤子函數可以做到AOP的效果,分別在執行前,執行後。具體用途可以根據自己需求來進行自定義。

Queue maintenance

Method getQueue() allows access to the work queue for purposes of monitoring and debugging. Use of this method for any other purpose is strongly discouraged. Two supplied methods, remove(Runnable) and purge are available to assist in storage reclamation when large numbers of queued tasks become cancelled.

可獲取

Reclamation

A pool that is no longer referenced in a program AND has no remaining threads may be reclaimed (garbage collected) without being explicitly shutdown. You can configure a pool to allow all unused threads to eventually die by setting appropriate keep-alive times, using a lower bound of zero core threads and/or setting allowCoreThreadTimeOut(boolean).

可以通過設置讓線程被回收掉。

Extension example

Most extensions of this class override one or more of the protected hook methods. For example, here is a subclass that adds a simple pause/resume feature:

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
    private boolean isPaused;
    private ReentrantLock pauseLock = new ReentrantLock();
    private Condition unpaused = pauseLock.newCondition();

    public PausableThreadPoolExecutor(...) { super(...); }

    protected void beforeExecute(Thread t, Runnable r) {
      super.beforeExecute(t, r);
      pauseLock.lock();
      try {
        while (isPaused) unpaused.await();
      } catch (InterruptedException ie) {
        t.interrupt();
      } finally {
        pauseLock.unlock();
      }
    }

    public void pause() {
      pauseLock.lock();
      try {
        isPaused = true;
      } finally {
        pauseLock.unlock();
      }
    }

    public void resume() {
      pauseLock.lock();
      try {
        isPaused = false;
        unpaused.signalAll();
      } finally {
        pauseLock.unlock();
      }
    }
}

線程池的狀態

狀態 說明
RUNNING Accept new tasks and process queued tasks
SHUTDOWN Don’t accept new tasks, but process queued tasks
STOP Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks
TIDYING All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
TERMINATED terminated() has completed

狀態變換:

  • RUNNING -> SHUTDOWN
    On invocation of shutdown()
  • (RUNNING or SHUTDOWN) -> STOP
    On invocation of shutdownNow()
  • SHUTDOWN -> TIDYING
    When both queue and pool are empty
  • STOP -> TIDYING
    When pool is empty
  • TIDYING -> TERMINATED
    When the terminated() hook method has completed

關鍵屬性

名稱 含義
corePoolSize 核心線程池大小
maximumPoolSize 最大線程池大小
keepAliveTime 線程最大空閒時間
workQueue 線程等待隊列
threadFactory 線程創建工廠
handler 拒絕策略

參數最多的一個構造函數:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

項目中用到的線程池,在MDC中加入了追蹤每一個請求的traceId,通過log輸出:

public class SysThreadPool {

    private ThreadPoolExecutor threadPoolExecutor;

    private SysThreadPool(ThreadPoolExecutor threadPoolExecutor) {
        this.threadPoolExecutor = threadPoolExecutor;
    }

    public static void doExecute(Runnable task) {
        Map<String, String> context = MDC.getCopyOfContextMap();
        getInstance().threadPoolExecutor.execute(() -> {
            // 將父線程的MDC內容傳給子線程
            if(context != null){
                MDC.setContextMap(context);
            }
            try {
                task.run();
            } catch (Exception e){
                e.printStackTrace();
            }finally {
                // 清空MDC內容
                MDC.clear();
            }
        });
    }

    private static SysThreadPool getInstance() {
        return InstanceHolder.threadPool;
    }
		// 單例模式的容器實現方式
    private static class InstanceHolder {

        private static final Integer CORE_POOL_SIZE = 50;
        private static final Integer MAX_POOL_SIZE = 500;
        private static final Long KEEP_ALIVE_TIME = 2L;
        private static final TimeUnit TIME_UNIT = TimeUnit.MINUTES;
        private static final LinkedTransferQueue QUEUE = new LinkedTransferQueue();
        private static final ThreadFactory FACTORY = new ThreadFactory() {
            private final AtomicInteger integer = new AtomicInteger();

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "SysThreadPool thread: " + integer.getAndIncrement());
            }
        };
        public static final SysThreadPool threadPool = new SysThreadPool(
                new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TIME_UNIT, QUEUE, FACTORY));
    }
}

貌似有點問題,MAX_POOL_SIZE應該沒有生效,因爲隊列是無界隊列。

JDK預置線程池

打開Executors.java,裏面很多static方法,且它的構造方法時私有的,所以這個類的用處也很明顯,只是作爲一個Facade。

/** Cannot instantiate. */
private Executors() {}
  • newCachedThreadPool。這種Queue的策略,對應前面所描述的第一種,Direct Handsoff。
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

核心數爲0,最大線程數相當於無界,而SynchronousQueue則是一個空隊列,不能存放任務,所以會直接創建線程,直到達到最大的線程數。所有閒置60s之後的線程會被回收,因此執行完之後不會佔用任何資源。

  • newFixedThreadPool。這種Queue策略,對應前面所描述的第二種,Unbounded queues。
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

核心數等於最大線程數,因爲LinkedBlockingQueue無界,所以最大線程數無實際意義。所以這種線程池保證最大線程數爲nThreads,且由於keepAliveTime爲0,所以不對線程進行回收;後來的任務全部扔進queue裏面,等待空閒的線程來執行。

  • newSingleThreadExecutor。這種Queue策略,對應前面描述的第二種,Unbounded queues。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

在FixThreadPool的基礎上,保證線程數最大爲1。

線程池如何工作

一般通過線程池的execute方法執行任務,在execute方法中,變濃縮了上面 內容的處理邏輯。

/*
 * 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.
 *
 * 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.
 */

主要分三步:①與corePoolSize比較大小,小就新建線程運行任務。②大於corePoolSize,放入隊列。③放入隊列失敗,再次嘗試新建線程執行任務,上限是maximumPoolSize。④創建新線程失敗,那麼調用拒絕策略。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
  	// ①與corePoolSize比較大小,小就新建線程運行任務。
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
  	// ②大於corePoolSize,放入隊列。
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
  	// ③放入隊列失敗,再次嘗試新建線程執行任務,上限是maximumPoolSize。
    else if (!addWorker(command, false))
      	// ④創建新線程失敗,那麼調用拒絕策略。
        reject(command);
}

其中比較關鍵的一個步驟可能是addWorker這個方法,在這其中,會調用threadFactory新建線程,並調用Thread的start()方法來開啓線程。

其中有一段標號與break、continue的使用,之前都沒見過、沒用過。

①可以給語句塊加標號賦予它們名稱,標號位於語句之前,且語句前只允許加一個標號,標號後面不能跟大括號,且後面只能跟for.while.do-while等循環。

②標號只能被continue和break引用。通過用標號,我們可以對外層循環進行控制。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (int c = ctl.get();;) {
        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;

        for (;;) {
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
          	// 通過CAS更新工作線程數量
            if (compareAndIncrementWorkerCount(c))
              	// break外層循環
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateAtLeast(c, SHUTDOWN))
              	// continue外層循環
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
      	// 調用threadFactory新建線程
        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 c = ctl.get();

                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && 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(w);
    }
    return workerStarted;
}

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

reject即直接調用rejectedExecution方法。

final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

線程池如何拉取隊列中的任務並執行?

這裏有一個細節,需要注意,Worker這個私有內部類,它實現了Runnable,並且通過threadFactory創建線程的時候,將自己作爲Runnable,傳給了創建的線程,而同時傳進去的task被專門用firstTask保存起來。

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

所以,上面的t.start()

if (workerAdded) {
    // 啓動線程
    t.start();
    workerStarted = true;
}

實際上是執行了Worker的run方法,也就是:

public void run() {
    runWorker(this);
}

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
  	// 第一次執行時,會首先執行之前傳入的task。
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
      	// getTask()會堵塞線程,直到能拿到數據。
        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);
                try {
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

其中還包含beforeExecute、afterExecute函數的執行。

Reference:

https://www.jianshu.com/p/f030aa5d7a28

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

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