面试准备--线程池

206 阅读5分钟

ThreadPoolExecutor

构造方法

ThreadPoolExecutor类实现了四个构造方法,最重要的如下,其他构造方法都是调用了这个方法实现的。

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;
    }

这个构造方法一共有七个参数:

  • corePoolSize:核心线程数,除非设置了 allowCoreThreadTimeOut ,否则就算这些线程是闲置的,也一直保持在线程池中。创建线程池后,每提交一个任务,即使有闲置的线程,也会创建一个新的线程,直到线程总数达到核心线程数。

  • maximumPoolSize:最大线程数。线程池中允许的最大线程数。当工作队列塞满时,会创建新的线程,直到线程数小于最大线程数。如果工作队列是一个无限队列,则最大线程数不起作用。

  • keepAliveTime:存活时间。当线程池中的线程数比核心线程数多,闲置线程的等待时间。超过这个时间的闲置线程将被清除,直到线程池里的线程数等于核心线程数。

  • unit:存活时间 keepAliveTime的单位。

  • workQueue:工作队列。用于存放等待执行的任务的阻塞队列。
    jdk提供了以下几种阻塞队列:

    • ArrayBlockingQueue:基于数组实现的有限阻塞队列。
    • LinkedBlockingQueue:基于单链表实现的无限阻塞队列。
    • SynchronousQuene:没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。
    • priorityBlockingQuene:具有优先级的无界阻塞队列
  • threadFactory:线程工厂。通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory

  • handler:拒绝策略。当工作队列满了,且没有空闲的线程。如果有新的任务提交,对于新提交的任务的处理策略,jdk提供了四种策略。

    • AbortPolicy:拒绝策略,直接抛出异常,拒绝新任务的提交。线程池的默认策略。
    • CallerRunsPolicy:调用线程执行策略。用调用者所在现场来执行任务。当线程池关闭时,该任务被丢弃。
    • DiscardPolicy:丢弃策略。直接丢弃该任务。
    • DiscardOldestPolicy:丢弃最早任务策略。

内部状态

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

ctl变量的高三位用来表示线程池的五种状态,低29位用来表示线程池中线程的数量。

  • RUNNING:运行状态,接受新的任务并执行。
  • SHUTDOWN:关闭状态,不接受新的任务,但是执行已经接受等待中的任务。
  • STOP:停止状态,不接受新任务,不执行等待的任务,并且中止正在执行的任务。
  • TIDYING:整理状态,所有任务停止,workerCount为0,即将执行terminated()方法。
  • TERMINATED:停止状态,terminated()方法执行完毕。

任务执行

当向线程池提交任务后,可以通过execute(Runnable command),submit()方法来执行任务。

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);
    }

具体的执行步骤如下:

  1. 如果线程池中的线程数小于核心线程数,执行addWorker()方法,创建一个新的线程执行该任务。
  2. 如果线程池处于RUNNING状态,且该任务已经在工作队列中等待执行,执行步骤三,否则,执行步骤4
  3. 如果线程池不处于RUNNING状态,且该任务已经从工作队列中移除,拒绝任务。
  4. 如果新建线程失败,那么可能是工作队列饱和或者线程池shut down,按照拒绝策略,拒绝任务。

之所以要double-check线程池的状态,是因为在多线程环境下,线程池的状态时刻在变化,而ctl.get()是非原子操作,很有可能刚获取了线程池状态后线程池状态就改变了。判断是否将command加入workque是线程池之前的状态。倘若没有double check,万一线程池处于非running状态,那么command永远不会执行。 addWorker方法:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        // 使用cas+自旋来增加线程
        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;
    }

addWorker方法,负责新建线程并执行任务。

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;
    }

调用submit方法,将返回结果封装到了Future中,可以通过返回值判断是否任务执行完成。 调用submit(Runnable task, T result), submit(Callable task)方法,可以通过Future的get方法,获取返回值。

异常处理

一般有以下四种处理异常的方法。

  1. try catch 手动捕获
  2. 使用submit方法提交任务,在使用Future的get方法获取返回值时,捕获异常
  3. 重写ThreadPoolExecutor的afterExecute方法
  4. 为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常