Java线程池核心类ThreadPoolExecutor

  通常,当需要同时处理的任务比较多时,为了避免为每个任务开一个线程(因为这样会导致频繁的线程开启和销毁,开销较大),采用线程池技术来进行线程资源的复用。
  在应用中,我们通常使用Executors类提供的静态方法来使用线程池:

ExecutorService exec0 = Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
ExecutorService exec1 = Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
ExecutorService exec2 = Executors.newFixedThreadPool(int);  //创建固定容量大小的缓冲池

  其中ExecutorService是一个接口,实现线程池的核心是一个ThreadPoolExecutor。将任务比如一个Runnable,或者一个Callable作为参数传入submit方法即完成任务向线程池的提交,将在线程池中自动执行该任务。返回参数可以是一个Future<?>对象。

  那么线程池内部具体是怎么实现的,诸如ExecutorService接口和和TheadPoolExecutor又有什么关系呢?下面通过jdk中的源码来学习一下。

与ThreadPoolExecutor类相关的接口和类的关系

public class ThreadPoolExecutor extends AbstractExecutorService{...};
public abstract class AbstractExecutorService implements ExecutorService {...}
public interface ExecutorService extends Executor{...}
public interface Executor {
    void execute(Runnable command);
}

  可以清楚地看到它们之间的关系,Executor 接口只声明了一个方法,而ExecutorService接口在其基础上扩展出了一些方法:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

  其中的submit是我们比较关注的方法,也就是核心的提交任务的方法。
  AbstractExecutorService则实现了上述方法,比如如下三个版本的submit方法实现:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

  可以看到,提交任务的方式可以是Runnable也可以是Callable,并且为了最终可以返回参数,同意采用了将task包装成RunnableFuture,该接口双重继承了Runnable接口和Future接口。然后就是execute方法的调用,execute方法的实现交给了继承AbstractExecutorService类的子类来完成,这里就是我们要学习的核心类:ThreadPoolExecutorService。

核心类ThreadPoolExecutorService

  其核心的构造器接口如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

  其中corePoolSize、maximumPoolSize分别表示核心池的大小,最大线程池的大小。keepAliveTime和空闲线程的存活时间有关,workQueue是一个阻塞队列,用于缓存任务,后面两个参数分别用于产生线程和拒绝服务的处理方式。

  首先,我们想搞清楚当一个任务通过线程池submit后,是怎么被执行的而submit实际上也就是调用了execute方法,所以execute就是线程池最核心的部分。
这篇博客中介绍的比较清楚了,但是我看了下我jdk版本是1.8.0_40,发现execute的实现和文章中介绍的有差异,细看了一下基本实现思路应该还是一样的。
在我的jdk版本中,execute方法是这样的:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        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)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

  先是判断当前线程池中的线程数和corePoolSize的大小,如果小于,则直接为新进的任务增加一个工作线程addWorker(command, true),并返回。否则尝试将任务添加到阻塞队列中,后面是检查一些异常情况,如是否外部将线程池停止isRunning(recheck)等。最后还有拒绝服务的情况。

  下面我们再来看addWorker

    private boolean addWorker(Runnable firstTask, boolean core) {
        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;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        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(w);
        }
        return workerStarted;
    }

  这个方法用于增加工作线程,所以传入的参数其中的任务叫firstTask, 可以看到前面一部分还用到了跳转指令,retry点。for(;;){}一部分还没看太懂,大致可以看出是对非正常执行情况的处理,都是一些跳转或返回。
  后面的try块是我们关注的重点,首先将firstTask包装成了一个Worker类,还获取了Worker的线程得到t,向workers这个HashSet中添加工作线程,最后调用t.start(); 来启一个工作线程。
  我们再来看Worker类:

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{...}

  再来看其核心run()方法,

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

  调用了ThreadPoolExecutor的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 ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    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);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

  到这个方法执行时,工作线程已经启动了,进入这个方法后就不断在任务缓冲队列中提取任务执行,执行的过程中对该Worker加锁,直到任务队列为空。

  注意到最后的finally语句块中的processWorkerExit(w, completedAbruptly);,查看源代码

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

  可以看到,从workers中将移出该工作线程。还可以看到allowCoreThreadTimeOut参数对行为的影响:该参数为false,则对core threads在即使空闲的情况下也不会被杀死,如果为true,则空闲的线程在一定的timeout后将被系统回收。在程序中就是若为false,则min=corePoolSize,如果当前剩余的工作线程大于等于该值,则不需要添加idle线程,否则需要增加一个idle线程,因为刚刚kill了一个工作完成的线程。

  另外,还有线程池的关闭,shutdown()和shutdownNow(),前者不会立即终止线程,而是等待任务缓冲队列中的的任务被完全执行后在结束,但是期间不再接受新的任务。后者则是立即终止所有的线程,并且清空缓冲队列,返回尚未执行的任务。

应用

  一般使用时,我们不是直接使用ThreadPoolExecutor类,而是通过文章开头时介绍的Executors工具类的静态方法来获得该类的实例对象。这就避免了繁杂的参数配置可能导致的潜在错误。

如何配置线程池的大小问题

  一个策略就是根据任务的类型来配置线程池的大小:
  对CPU密集型任务,我们采用Ncpu+1,对IO密集型任务,我们采用2*Ncpu。

发布了59 篇原创文章 · 获赞 9 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章