浅析Java线程池

142 阅读27分钟
  1. Java 线程池有哪些属性?
  2. 提交任务时队列满了怎么办?
  3. 什么情况下会创建新线程?
  4. 提交任务时线程池满了怎么办?
  5. 空闲线程怎么关闭?
  6. 线程池执行顺序是怎样的?如何让线程池按照 core、max、queue 的执行顺序去执行?
  7. Executors.newFixedThreadPool(...) 和 Executors.newCachedPool(...) 构造出来的线程池有哪些差异?
  8. 子线程抛出的异常,主线程能感知到吗?任务执行过程中发生异常,怎么处理?
  9. 什么时候会执行拒绝策略?
  10. 任务执行结果是怎么获取的?流程是怎样的?

总览

先看下 Java 线程池几个相关类的继承结构:

Executor -> ExecutorService -> AbstractExecutorService -> ThreadPoolExecutor 的继承结构。

Executor

Executor 接口非常简单只有一个 void execute(Runnable command) 方法代表提交一个任务。

我们经常这样显式的启动一个线程:

new Thread(new Runnable(){
  // do something
}).start();

使用线程池后我们可以这样执行任务:

Executor executor = new SerialExecutor();
executor.execute(() -> {...});

如果我们希望线程池同步执行任务,我们可以这样实现 Executor 接口:

class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();// 这里不是用的 new Thread(r).start(),也就是说没有启动任何一个新的线程。
    }
}

想要异步执行任务:

class ThreadPerTaskExecutor implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();  // 每个任务都用一个新的线程来执行
    }
}

看下如何实现,所有任务加入一个 queue 中,然后从 queue 中取任务交给真正的 Executor 执行:

public class SerialExecutor implements Executor {

    final Queue<Runnable> tasks = new ArrayDeque<>(10);
    final Executor executor;
    Runnable active;

    public SerialExecutor(Executor executor) {
        this.executor = executor;
    }
		
  	// 提交任务到线程池:将任务添加到任务队列,scheduleNext 触发执行器去任务队列取任务执行
    @Override
    public synchronized void execute(@Nonnull Runnable command) {
        tasks.offer(() -> {
            try {
                command.run();
            } finally {
                this.scheduleNext();
            }
        });
    }

    private void scheduleNext() {
        if ((active = tasks.poll()) != null) {
            // 具体执行转给真正的执行器
            executor.execute(active);
        }
    }
}

Executor 只有提交任务功能,没法实现查看当前任务队列剩余多少任务?任务队列满了怎么办?当前线程池有多少线程活着?都是这个接口不足之处,所以要介绍下面这个接口:ExecutorService

ExecutorService

一般我们定义一个线程池时,往往都是使用的这个接口:

ExecutorService executor = Executors.newFixedThreadPool(args...);
ExecutorService executor = Executors.newCachedThreadPool(args...);

先简单过一下这个接口有哪些方法。

public interface ExecutorService extends Executor {
		// 关闭线程池,已提交的任务继续执行,不继续接收新任务
    void shutdown();
  
		// 关闭线程池,非但不继续接收新任务,还将停止当前正在执行的任务
    List<Runnable> shutdownNow();
  
  	// 线程池是否关闭
    boolean isShutdown();
  
		// 如果调用了 shutdown() 或 shutdownNow() 后所有任务都结束了,则返回 true
  	// 这个方法必须在 shutdown() 或 shutdownNow() 调用之后才会返回 true
    boolean isTerminated();
  
		// 等待所有任务完成,并设置超时时间
  	// 实际应用是:调用关闭线程池方法后,调用这个方法阻塞等待池中所有任务完成。
  	// 当超时或者发起 awaitTermination 的线程被中断时,从阻塞等待中返回。
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

  	// 提交一个 Callable 任务
    <T> Future<T> submit(Callable<T> task);

  	// 提交一个 Runnable 任务,第二个参数会放到 FutureTask 中,作为返回值
  	// 因为 Runnable 的 run 方法并不返回任何东西
    <T> Future<T> submit(Runnable task, T result);

  	// 提交一个 Runnable 任务
    Future<?> submit(Runnable task);
		
	  // 执行所有的任务,返回 Future 集合
    <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;
		// 同只要有一个任务结束了就返回,返回执行完的那个任务的结果
  	// 但这个带超时,超过执行时间后抛出 TimeoutException
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService 提供了比 Executor 接口更丰富的方法,能提交任务,关闭线程池,阻塞等待线程池所有任务都结束,能获取结果。

FutureTask

在继续介绍 ExecutorService 实现类前,先来说说相关类 FutureTask,提交给线程池的任务 Runnable 都是先封装成 FutureTask。

Future      Runnable
   \           /
    \         /
   RunnableFuture
          |
          |
      FutureTask
  
public class FutureTask<V> implements RunnableFuture<V> {
  	private volatile int state;
  	// runnable 被适配成 Callable 实现类是Executors.RunnableAdapter
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;
  	.....
}

# Executors.RunnableAdapter 503
static final class RunnableAdapter<T> implements Callable<T> {
  			// 传入的 runnable
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

FutureTask 通过 RunnableFuture 间接实现了 Runnable,所以每个 Runnable 通常是经过 RunnableAdapter 适配成 Callable 转换成 FutureTask 类型。然后调用 executor.execute() 方法将任务 FutureTask 提交给线程池。

Runnable 方法是没有返回值的,可以在 submit 的第二个参数指定一个返回值T实例对象(不是 Class 类型)同时要求 Runnable 内组合一个 T 实例对象,通过构造方法传入。

<T> Future<T> submit(Runnable task, T result);

Mail mail = new Mail();
// 传入相同的对象,run 方法可以间接使用该对象通信
Future<Mail> future = pool.submit(new MessageRunnable(mail), mail);
System.out.println(future.get().getTitle());

private static class MessageRunnable implements Runnable {
  
  private Mail mail;
  
  private MessageRunnable(Mail mail) {
    this.mail = mail;
  }

  @Override
  public void run() {
    mail.setTitle("123");
  }
}

另外还可以实现 Callable 接口,传回返回值。当运行出现异常时,call 会抛出异常但 run 不会抛异常。

public abstract void run();

V call() throws Exception;

下面我们来看 ExecutorService 的抽象实现 AbstractExecutorService。

AbstractExecutorService

AbstractExecutorService 抽象类派生自 ExecutorService 接口,实现了 submit 三个复载方法,invokeAny, invokeAll 方法,以及添加了 两个 newTaskFor 方法。顶层 Executor 接口的 execute void execute(Runnable command) 不需要返回结果,所以不会封装成 FutureTask。

// newTaskFor-Callable
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
// newTaskFor-Runnable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

AbstractExecutorService 抽象类中 invokeAll invokeAny 占方法体绝大部分篇幅,实战使用很少不具备文章承前启后功能,略过。

public abstract class AbstractExecutorService implements ExecutorService {

    // RunnableFuture 是用于获取执行结果的,我们常用它的子类 FutureTask
    // 下面两个 newTaskFor 方法用于将我们的任务包装成 FutureTask 提交到线程池中执行
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

    // 提交任务
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        // 1. 将任务包装成 FutureTask
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        // 2. 交给执行器执行,execute 方法由具体的子类来实现
        // 前面也说了,FutureTask 间接实现了Runnable 接口。
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        // 1. 将任务包装成 FutureTask
        RunnableFuture<T> ftask = newTaskFor(task, result);
        // 2. 交给执行器执行
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        // 1. 将任务包装成 FutureTask
        RunnableFuture<T> ftask = newTaskFor(task);
        // 2. 交给执行器执行
        execute(ftask);
        return ftask;
    }

    // 此方法目的:将 tasks 集合中的任务提交到线程池执行,任意一个线程执行完后就可以结束了
    // 第二个参数 timed 代表是否设置超时机制,超时时间为第三个参数,
    // 如果 timed 为 true,同时超时了还没有一个线程返回结果,那么抛出 TimeoutException 异常
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                            boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (tasks == null)
            throw new NullPointerException();
        // 任务数
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        // 
        List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);

        // ExecutorCompletionService 不是一个真正的执行器,参数 this 才是真正的执行器
        // 它对执行器进行了包装,每个任务结束后,将结果保存到内部的一个 completionQueue 队列中
        // 这也是为什么这个类的名字里面有个 Completion 的原因吧。
        ExecutorCompletionService<T> ecs =
            new ExecutorCompletionService<T>(this);
        try {
            // 用于保存异常信息,此方法如果没有得到任何有效的结果,那么我们可以抛出最后得到的一个异常
            ExecutionException ee = null;
            long lastTime = timed ? System.nanoTime() : 0;
            Iterator<? extends Callable<T>> it = tasks.iterator();

            // 首先先提交一个任务,后面的任务到下面的 for 循环一个个提交
            futures.add(ecs.submit(it.next()));
            // 提交了一个任务,所以任务数量减 1
            --ntasks;
            // 正在执行的任务数(提交的时候 +1,任务结束的时候 -1)
            int active = 1;

            for (;;) {
                // ecs 上面说了,其内部有一个 completionQueue 用于保存执行完成的结果
                // BlockingQueue 的 poll 方法不阻塞,返回 null 代表队列为空
                Future<T> f = ecs.poll();
                // 为 null,说明刚刚提交的第一个线程还没有执行完成
                // 在前面先提交一个任务,加上这里做一次检查,也是为了提高性能
                if (f == null) {
                    if (ntasks > 0) {
                        --ntasks;
                        futures.add(ecs.submit(it.next()));
                        ++active;
                    }
                    // 这里是 else if,不是 if。这里说明,没有任务了,同时 active 为 0 说明
                    // 任务都执行完成了。其实我也没理解为什么这里做一次 break?
                    // 因为我认为 active 为 0 的情况,必然从下面的 f.get() 返回了

                    // 2018-02-23 感谢读者 newmicro 的 comment,
                    //  这里的 active == 0,说明所有的任务都执行失败,那么这里是 for 循环出口
                    else if (active == 0)
                        break;
                    // 这里也是 else if。这里说的是,没有任务了,但是设置了超时时间,这里检测是否超时
                    else if (timed) {
                        // 带等待的 poll 方法
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                        // 如果已经超时,抛出 TimeoutException 异常,这整个方法就结束了
                        if (f == null)
                            throw new TimeoutException();
                        long now = System.nanoTime();
                        nanos -= now - lastTime;
                        lastTime = now;
                    }
                    // 这里是 else。说明,没有任务需要提交,但是池中的任务没有完成,还没有超时(如果设置了超时)
                    // take() 方法会阻塞,直到有元素返回,说明有任务结束了
                    else
                        f = ecs.take();
                }
                /*
                 * 我感觉上面这一段并不是很好理解,这里简单说下。
                 * 1. 首先,这在一个 for 循环中,我们设想每一个任务都没那么快结束,
                 *     那么,每一次都会进到第一个分支,进行提交任务,直到将所有的任务都提交了
                 * 2. 任务都提交完成后,如果设置了超时,那么 for 循环其实进入了“一直检测是否超时”
                       这件事情上
                 * 3. 如果没有设置超时机制,那么不必要检测超时,那就会阻塞在 ecs.take() 方法上,
                       等待获取第一个执行结果
                 * 4. 如果所有的任务都执行失败,也就是说 future 都返回了,
                       但是 f.get() 抛出异常,那么从 active == 0 分支出去(感谢 newmicro 提出)
                         // 当然,这个需要看下面的 if 分支。
                 */



                // 有任务结束了
                if (f != null) {
                    --active;
                    try {
                        // 返回执行结果,如果有异常,都包装成 ExecutionException
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }// 注意看 for 循环的范围,一直到这里

            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
            // 方法退出之前,取消其他的任务
            for (Future<T> f : futures)
                f.cancel(true);
        }
    }

    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
        try {
            return doInvokeAny(tasks, false, 0);
        } catch (TimeoutException cannotHappen) {
            assert false;
            return null;
        }
    }

    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        return doInvokeAny(tasks, true, unit.toNanos(timeout));
    }

    // 执行所有的任务,返回任务结果。
    // 先不要看这个方法,我们先想想,其实我们自己提交任务到线程池,也是想要线程池执行所有的任务
    // 只不过,我们是每次 submit 一个任务,这里以一个集合作为参数提交
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            // 这个很简单
            for (Callable<T> t : tasks) {
                // 包装成 FutureTask
                RunnableFuture<T> f = newTaskFor(t);
                futures.add(f);
                // 提交任务
                execute(f);
            }
            for (Future<T> f : futures) {
                if (!f.isDone()) {
                    try {
                        // 这是一个阻塞方法,直到获取到值,或抛出了异常
                        // 这里有个小细节,其实 get 方法签名上是会抛出 InterruptedException 的
                        // 可是这里没有进行处理,而是抛给外层去了。此异常发生于还没执行完的任务被取消了
                        f.get();
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    }
                }
            }
            done = true;
            // 这个方法返回,不像其他的场景,返回 List<Future>,其实执行结果还没出来
            // 这个方法返回是真正的返回,任务都结束了
            return futures;
        } finally {
            // 为什么要这个?就是上面说的有异常的情况
            if (!done)
                for (Future<T> f : futures)
                    f.cancel(true);
        }
    }

    // 带超时的 invokeAll,我们找不同吧
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        if (tasks == null || unit == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks)
                futures.add(newTaskFor(t));

            long lastTime = System.nanoTime();

            Iterator<Future<T>> it = futures.iterator();
            // 每提交一个任务,检测一次是否超时
            while (it.hasNext()) {
                execute((Runnable)(it.next()));
                long now = System.nanoTime();
                nanos -= now - lastTime;
                lastTime = now;
                // 超时
                if (nanos <= 0)
                    return futures;
            }

            for (Future<T> f : futures) {
                if (!f.isDone()) {
                    if (nanos <= 0)
                        return futures;
                    try {
                        // 调用带超时的 get 方法,这里的参数 nanos 是剩余的时间,
                        // 因为上面其实已经用掉了一些时间了
                        f.get(nanos, TimeUnit.NANOSECONDS);
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    } catch (TimeoutException toe) {
                        return futures;
                    }
                    long now = System.nanoTime();
                    nanos -= now - lastTime;
                    lastTime = now;
                }
            }
            done = true;
            return futures;
        } finally {
            if (!done)
                for (Future<T> f : futures)
                    f.cancel(true);
        }
    }

}

到这里我们发现 invokeAll, invokeAny, submit 方法都只是将任务 FutureTask 通过 execute 方法提交给线程池,还没有出现真正开启线程执行任务的 execute 方法体,这个就在下文 ThreadPoolExecutor 类中。

ThreadPoolExecutor

概览

ThreadPoolExecutor 是 JDK 中线程池的实现,它实现了任务提交,任务管理、监控方法。我们先回顾下提交任务的三个方法:

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 方法中的参数 Runnable, Callable 不适用于 new Thread(runnable).start() 开启线程用的 runnable,而是指定线程负责的任务,线程要做的事情在 Runnable 的 run 或者 Callable 的 call 方法中指定。

通过 Javadoop 博主的配图看下线程池的主要构件:

构造方法

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:核心线程数
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
  • TimeUnit:超时时间单位
  • keepAliveTime:空闲线程存活时间,设置 allowCoreThreadTimeOut(true) 也可以让核心线程超时后被回收。
  • BlockingQueue:任务队列。可选 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue
  • threadFactory:线程工厂。可以指定有业务含义的线程名以及线程内部抛异常时的异常处理器 UncaughtExceptionHandler。
  • RejectedExecutionHandler:拒绝策略。CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。

线程池状态和线程数

除了上述7个参数外,线程池使用一个 32 位的整数存放线程池的状态及当前池中的线程数。其中高 3 位存放线程池的状态,低 29 位存放线程数。

补充机组知识:

  1. 原码、补码、反码
  2. & | ~ >> << 运算
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 这里 COUNT_BITS 设置为 29(32-3),意味着前三位用于存放线程状态,后29位用于存放线程数
// 很多初学者很喜欢在自己的代码中写很多 29 这种数字,或者某个特殊的字符串,然后分布在各个地方,这是非常糟糕的
private static final int COUNT_BITS = Integer.SIZE - 3;

// 000 11111111111111111111111111111
// 这里得到的是 29 个 1,也就是说线程池的最大线程数是 2^29-1=536870911
// 以我们现在计算机的实际情况,这个数量还是够用的
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 我们说了,线程池的状态存放在高 3 位中
// 运算结果为 111跟29个0:111 00000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS;
// 000 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;

// 将整数 c 的低 29 位修改为 0,就得到了线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 将整数 c 的高 3 为修改为 0,就得到了线程池中的线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 存放线程池状态和线程数的 32 整数
private static int ctlOf(int rs, int wc) { return rs | wc; }

/*
 * Bit field accessors that don't require unpacking ctl.
 * These depend on the bit layout and on workerCount being never negative.
 */

private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

这介绍下线程池的状态和状态转换的过程:

  • RUNNING:接收新的任务,处理队列中的任务。
  • SHUTDOWN:不接收新的任务提交,但会处理已提交到等待队列的任务。
  • STOP:不接收新的任务提交,而且不再处理队列中的任务,中断当前正在执行的线程。
  • TIDYING:所有任务都销毁了,workCount 为 0。线程池的状态在转 TIDYING 时会调用钩子函数 terminated()。
  • TERMINATED:terminated() 方法结束后,线程池状态变成这个。

各个状态的转换过程有如下几种:

  • RUNNING -> SHUTDOWN:当调用 shutdown() 后会走这个状态转换。
  • RUNNING | SHUTDOWN -> STOP:当调用 shutdownNow() 后会进行这个状态转换。
  • SHUTDOWN -> TIDING:等待队列和线程池任务都清空后,进行这个状态转换。
  • STOP -> TIDING:当任务队列清空后,进行这个状态转换。
  • TIDING -> TERMINATED:当 terminated() 方法执行结束后。

Worker 内部类

线程池中的线程被包装成 Worker,任务是 Runnable(内部变量名叫 task 或 command),线程是 Worker

Worker 实现了 Runnable 接口,当线程启动会调用 Worker.run 方法,继续调用 ThreadPoolExecutor.runWorker 方法。Worker 还继承了 AQS 用于实现排它锁。

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;

    // 这个是真正的线程,任务靠你啦
    final Thread thread;

    // 前面说了,这里的 Runnable 是任务。为什么叫 firstTask?因为在创建线程的时候,如果同时指定了
    // 这个线程起来以后需要执行的第一个任务,那么第一个任务就是存放在这里的(线程可不止执行这一个任务)
    // 当然了,也可以为 null,这样线程起来了,自己到任务队列(BlockingQueue)中取任务(getTask 方法)就行了
    Runnable firstTask;

    // 用于存放此线程完成的任务数,注意了,这里用了 volatile,保证可见性
    volatile long completedTasks;

    // Worker 只有这一个构造方法,传入 firstTask,也可以传 null
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 调用 ThreadFactory 来创建一个新的线程,将 worker 自己传给 Thread 的 runnable 属性
        this.thread = getThreadFactory().newThread(this);
    }

    // 这里调用了外部类的 runWorker 方法
    public void run() {
        runWorker(this);
    }

    ...// 其他几个方法没什么好看的,就是用 AQS 操作,来获取这个线程的执行权,用了独占锁
}

讲过前面的铺垫,进入正文 submit 最终导向的 ThreadPoolExecutor.execute() 方法。

execute

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
  			// 标识线程池状态和线程数的整数
  			// ctl 是 AutomicInteger 底层的 volatile 保证了可见性
        int c = ctl.get();
  			// 如果当前线程数少于核心线程数,那么直接添加一个worker来执行任务
  			// 创建一个核心线程,并把当前任务 command 作为这个线程的第一个任务 firstTask
        if (workerCountOf(c) < corePoolSize) {
          	// 添加任务成功,execute 方法就结束了;提交任务嘛,线程池接收任务,这个方法也就可以返回了。
          	// 而任务的执行则是在 addWorker 构建 Worker 时开启新线程异步执行的。
            if (addWorker(command, true))
                return;
          	// 到这里说明 addWorker 失败
            c = ctl.get();
        }
  			// 到这里说明【线程数>=corePoolSize】或者【addWorker 失败了】
  			// 如果线程池是运行状态,任务command 入队任务队列 workQueue
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
          	// 如果线程池已经不处于 Running 状态那么移除任务 command,并且执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
          	// 如果线程池还在运行中,但线程数为0 那么开启新线程
          	// 这块代码的意图是:担心任务都提交到队列中了,但线程都关闭了
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) // 如果 workQueue 已满那么以 maximumPoolSize 为界创建新线程
          	// 如果失败,说明当前线程数达到 maximumPoolSize,执行拒绝策略
            reject(command);
    }
  1. 当前线程数小于 corePoolSize 时,开启一个新线程执行任务
  2. 当前线程数大于等于 corePoolSize 时,任务进入工作队列 workQueue 等待被执行
  3. 工作队列满载时,判断当前线程数是否小于 maximumPoolSize,开启非核心线程否则执行拒绝策略。
  4. 非核心线程在空闲超过 keepAliveTime 时间后被回收,也可以调用 allowCoreThreadTimeOut(true) 设置核心线程超时后被回收
  5. JDK自带的拒绝策略有 CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。
addWorker

addWorker(Runnable firstTask, boolean core) 方法负责创建工作者线程。

// 第一个参数 firstTask 是准备提交给这个线程执行的任务,可以为 null
// 第二个参数 core 代表要创建的是核心线程还是非核心线程
// true 代表使用核心线程数 corePoolSize 作为创建线程的界限,即如果线程数已经达到 corePoolSize,那么就不能响应这次创建线程的请求
// false 代表使用最大线程数 maximumPoolSize 作为创建线程的界限
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 牢记线程池状态是 SHUTDOWN 时,不允许提交新任务,但允许工作队列中的任务继续执行
          	// 如果线程池已关闭当满足下面条件之一时,不允许创建 worker:
          	// 1. 线程池状态 > SHUTDOWN 也就是 STOP、TIDYING 或 TERMINATED
          	// 2. firstTask != null 即线程池状态是 SHUTDOWN 但还有任务要提交,拒绝创建 worker
          	// 3. workQueue.isEmpty 即线程池状态是 SHUTDOWN 但工作队列 workQueue 已经空了,拒绝创建 worker
          	// 多说一句,如果线程池状态是 SHUTDOWN 但 firstTask == null 且 workQueue 非空,是允许创建 worker 的
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
              	// 如果 core == true 代表使用 corePoolSize 为界创建 worker 否则使用 maximusPoolSize 为界。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
              	// 如果 cas 成功,那么所有创建线程前的条件都已满足,跳出外层 for 循环,准备创建线程执行任务
              	// 如果 cas 失败,说明有其他线程在尝试往线程池中创建线程,继续判断线程池状态决定走内层还是外层循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
              	// 由于有并发,重新再读一下 ctl
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs) 
	                  // 如果线程池状态发生改变,存在其他线程关闭了线程池的可能,从外层 for 循环继续,否则进入内层 for 循环
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
				// 到这里我们认为可以开始创建 worker 了,至少当前是满足所有校验条件的
  			// worker 是否已经启动
        boolean workerStarted = false;
  			// 是否已将这个 worker 添加到 workers 这个 HashSet 中
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask); // 将 firstTask 传给这个 worker,可能是 null
            // 取 worker 中的线程对象,Worker 构造方法会调用 ThreadFactory 创建一个线程
          	// 当前的Worker对象作为线程的 runnable 属性,线程启动时调用当前Woker对象的 run 方法
            final Thread t = w.thread; 
            if (t != null) {
              	// 这个是整个线程池的全局锁,保证下面「添加Worker到HashSet」「更新历史最大线程数」是线程安全的
              	// 而关闭一个线程池也需要持有 mainLock 全局锁,至少在 addWorker 持有锁期间,线程池不会被关闭
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // 重新获取线程池状态
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN || // 小于 SHUTDOWN 说明是 RUNNING
                        // 等于 SHUTDOWN 不接收新任务 firstTask==null,但会执行等待队列中的任务
                        (rs == SHUTDOWN && firstTask == null)) { 
                        if (t.isAlive()) // worker 里的 thread 可不能是已经启动的
                            throw new IllegalThreadStateException();
                        workers.add(w);
                      	// largestPoolSize 用于记录线程池历史最大线程数,因为 workers 是不断增加或减少的
                        int s = workers.size(); 
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) { // 添加worker成功的话,启动这个线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted) // 如果线程没有启动成功,需要做清理工作。比如前面加1的 workerCount 减1
                addWorkerFailed(w);
        }
        return workerStarted; // 返回线程是否启动成功
    }
runWorker

我们知道worker对象的线程启动后,会执行 worker 的 run 方法:

// Worker 类的 run() 方法
public void run() {
    runWorker(this);
}

继续往下看 runWorker 方法。

// 此方法由Worker线程启动后调用,使用while循环不断地从等待队列中获取任务并执行
// Worker在初始化时可以指定 firstTask,那么第一个任务也就不需要从workQueue中获取
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();
                // 线程池状态大于等于 STOP 意味着当前Worker线程也要中断
                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(); // 执行我们自己的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; // 置空 task 准备 getTask 获取下一个任务
                    w.completedTasks++; // 累加已完成的任务,Worker对象 completedTasks 属性值加一
                    w.unlock(); // 释放掉 worker 的独占锁
                }
            }
            completedAbruptly = false; // 工作队列为空,当前Worker线程正常退出
        } finally {
            // 到这里需要执行线程关闭
          	// 1. 说明 getTask 返回 null,队列中已经没有要执行的任务,执行关闭
          	// 2. 任务执行 run 方法过程中抛出异常
          	// 第一种情况,getTask 方法中已经将 workerCount 减一
          	// 第二种情况,workerCount 并没有进行处理,需要在 processWorkerExit 中将 workerCount 减一
            processWorkerExit(w, completedAbruptly);
        }
    }

我们继续看 getTask() 是怎么获取任务的。

getTask
// 整个方法有三种可能返回 null 来关闭线程:
// 1. 线程池状态大于等于STOP时,返回 null
// 2. 线程池状态等于SHUTDOWN且工作队列 workQueue 已空时,返回 null
// 3. 「线程数大于线程池最大线程数」或者「发生超时时」返回 null
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 1. 线程池状态大于等于STOP时,线程数 workCount--,返回 null 
          	// 2. 线程池状态等于 SHUTDOWN 且工作队列 workQueue 为空时,返回 null
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // 允许核心线程超时或者线程数 > 核心线程数时,允许超时
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
						// 以下两种情况均返回 null
          	// 1. 线程数大于线程池最大线程数
          	// 2. 允许超时且已超时
          	// 且线程数大于1或者工作队列已空,CAS 线程数减一,成功返回 null,否则继续循环
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ? // 允许超时,调用 poll 方法,超时时间 keepAliveTime
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take(); // 不允许超时,调用 take 方法阻塞等待任务
                if (r != null)
                    return r; 
                timedOut = true; // 如果超时时间 keepAliveTime 内未成功返回任务,设置超时标识
            } catch (InterruptedException retry) {
              	// 如果此 worker 发生了中断,采取重试策略。
              	// 至于为什么会发生中断,是因为开发者可以调用 setMaximumPoolSize 调小线程池最大线程数
              	// 意味着超出的部分线程需要关闭掉,再次进入 for 循环会由于 wc > maximumPoolSize 返回 null 而走关闭线程逻辑
                timedOut = false;
            }
        }
    }

到这里基本说完了 execute 方法的执行流程,我们再回到 execute(Runnable command) 方法,看看各个分支。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
  			// 标识线程池状态和线程数的整数
  			// ctl 是 AutomicInteger 底层的 volatile 保证了可见性
        int c = ctl.get();
  			// 如果当前线程数少于核心线程数,那么直接添加一个worker来执行任务
  			// 创建一个核心线程,并把当前任务 command 作为这个线程的第一个任务 firstTask
        if (workerCountOf(c) < corePoolSize) {
          	// 添加任务成功,execute 方法就结束了;提交任务嘛,线程池接收任务,这个方法也就可以返回了。
          	// 而任务的执行则是在 addWorker 构建 Worker 时开启新线程异步执行的。
            if (addWorker(command, true))
                return;
          	// 到这里说明 addWorker 失败
            c = ctl.get();
        }
  			// 到这里说明【线程数>=corePoolSize】或者【addWorker 失败了】
  			// 如果线程池是运行状态,任务 command 进入任务队列 workQueue
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
          	// 如果线程池已经不处于 Running 状态那么移除任务 command,并且执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
          	// 如果线程池还在运行中,但线程数为0 那么开启新线程
          	// 这块代码的意图是:担心任务都提交到队列中了,但线程都关闭了
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) // 如果 workQueue 已满那么以 maximumPoolSize 为界创建新线程
          	// 如果失败,说明当前线程数达到 maximumPoolSize,执行拒绝策略
            reject(command);
    }

拒绝策略

上面 execute 方法有两种情况会调用 reject(command) 方法拒绝任务,此时线程池已经不能接收任务。

final void reject(Runnable command) {
    // 执行拒绝策略
    handler.rejectedExecution(command, this);
}
  • 线程池已经不处于运行状态,对新的执行任务拒绝策略,不再接收新任务。
  • 工作队列 workQueue 已满且线程数达到线程池最大线程数 maximumPoolSize,执行拒绝策略。

接下来我们说一说 ThreadPoolExecutor 自带的四种拒绝策略。

// 只要线程池没有被关闭,那么由提交任务的线程自己来执行这个任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

// 不管怎样,直接抛出 RejectedExecutionException 异常
// 这个是默认的策略,如果我们构造线程池的时候不传相应的 handler 的话,那就会指定使用这个
public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

// 不做任何处理,直接忽略掉这个任务
public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

// 这个相对霸道一点,如果线程池没有被关闭的话,
// 把队列队头的任务(也就是等待了最长时间的)直接扔掉,然后提交这个任务到等待队列中
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

Executors 工具类

Executors 提供四类线程池静态构造方法:

  • newFixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    

    最大线程数设置为与核心线程数相等,此时 keepAliveTime 设置为 0 (这里的 keepAliveTime 即使设置为非0也是没用的,因为默认线程池不会回收核心线程,除非设置 allowCoreThreadTimeOut==true),任务队列使用无界队列 LinkedBlockingQueue 大小是 Integer.MAX_VALUE。

  • newSingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    

    和 newFixedThreadPool 一样,只要设置核心线程数和最大线程数都是1,线程池中只有一个核心线程。

  • newCachedThreadPool

    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    

    缓存线程池:核心线程数是0,需要的时候就创建一个新的线程,同时可以在复用已创建的线程,空闲超时时间是 60 秒,工作队列是 SynchronousQueue 本身不存储任何元素,必须要有任务或者工作者线程,否则阻塞等待。

    缓存线程池适用于任务可以较快速完成的情况,当线程空闲 60 秒没获取到任务时,将关闭此线程并从线程池中移除。所以线程池空闲很长时间也没有问题,随着所有县城超时被关闭,整个线程池不会占用任何的资源。

    缓存线程池的 execute 提交任务过程分析:corePoolSize == 0 所以任务将直接提交到工作队列 SynchronouseQueue,第一个任务提交时 workQueue.offer 方法肯定会返回 false,因为此时线程池中没有线程。将进入到最后一个分支 addWorker 创建第一个线程。之后再提交任务,将取决于是否有空闲的线程接受任务,有则进入第二个 if 分支,否则代表已创建的线程超时被回收,走最后的 else if 分支创建新线程。

    int c = ctl.get();
    // corePoolSize 为 0,所以不会进到这个 if 分支
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // offer 如果有空闲线程刚好可以接收此任务,那么返回 true,否则返回 false
    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. Java 线程池有哪些属性?

    • corePoolSize:核心线程数。默认情况下核心线程超时不回收,可以调用 setAllowCoreThreadTimeOut 方法回收核心线程。
    • maximumPoolSize:线程池最大线程数。线程池允许创建的最大线程数。线程池历史最大线程数可以查看ThreadPoolExecutor 的 largestPoolSize 属性。
    • BlockingQueue:任务队列。可选 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue。
    • TimeUnit:线程空闲超时时间单位。
    • keepAliveTime:线程空闲超时时间大小。
    • ThreadFactory:线程工厂,可以通过 Guava 的 ThreadFactoryBuilder 构建 ThreadFactory。
    • RejectedExecutionHandler:拒绝策略。可选 CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。
  2. 提交任务时工作队列 workQueue 满了怎么办?

    以线程池最大线程数 maximumPoolSize 为界,创建新线程处理任务,当线程数达到线程池最大线程数时,执行拒绝策略。

  3. 什么情况下会创建线程?

    • 当前线程数小于 corePoolSize 时创建核心线程

    • 当前线程数已经达到 corePoolSize 时,任务提交到工作队列 workQueue 等待核心线程取任务。

    • 工作队列已满时,以线程池最大线程数 maximumPoolSize 为界创建非核心线程,处理任务;如果线程数达到 maximumPoolSize 执行拒绝策略。

    注意工作队列采用 LinkedBlockingQueue 无界队列时,即使线程数达到 corePoolSize 线程数也不会再增长,因为后面的任务入队无界队列即可,此时的 maximumPoolSize 没什么意义。

  4. 提交任务时线程池满了,怎么办?

    线程数达到 maximumPoolSize 时执行拒绝策略,ThreadPoolExecutor 自带四种拒绝策略实现类。

    • CallerRunsPolicy:只要线程池没有关闭,任务由提交的线程自己来执行
    • AbortPolicy:直接抛出 RejectedExecutionException 异常
    • DiscardPolicy:忽略当前任务
    • DiscardOldestPolicy:将工作队列队头的任务(即等待时间最久的任务)移除,然后提交这个任务到工作队列
  5. 空闲线程怎么关闭?

    先说下空闲线程什么时间触发关闭,当 runWorker 方法通过 while 循环调用 getTask 返回 null 时,关闭当前线程。

    那什么时候 getTask 会返回 null 呢?

    • 线程池状态大于等于 STOP 时,线程池已关闭。
    • 线程池状态等于 SHUTDOWN 且工作队列 workQueue 为空
    • 当前线程数大于线程池最大线程数 maximumPoolSize
    • 当前的 Worker 线程空闲超时。调用超时 poll 返回 null,在超时时间内未获取到任务。
  6. 线程池的执行顺序是怎样的?如何让线程池按照 core、max、queue 的执行顺序去执行?

    1. 当前线程数小于核心线程数 corePoolSize 时创建核心线程
    2. 当前线程数达到核心线程数时,任务进入工作队列 workQueue,排队等待执行
    3. 当工作队列已满时,在不超过线程池最大线程数 maximumPoolSize 前提下,创建非核心线程。
    4. 当线程数达到线程池最大线程数 maximumPoolSize 时,执行拒绝策略。

    如何让线程池按照 core、max、queue 的执行顺序执行呢?即调整 2、3 步骤的执行顺序。

    通过自定义阻塞队列修改 offer() 方法逻辑,当线程数 < maximumPoolSize 时直接返回 false,execute 方法就会先创建非核心线程数,达到按照 core -> max -> queue 的执行顺序。

  7. Executors.newFixedThreadPool(...) 和 Executors.newCachedPool(...) 构造出来的线程池有哪些差异?

    • 核心线程数不同。newFixedThreadPool 核心线程数通常大于0,newCachedPool 核心线程数为0
    • 最大线程数不同。newFixedThreadPool 最大线程数等于核心线程数,newCachedPool 最大线程数是 Integer.MAX_VALUE
    • 工作队列不同。newFixedThreadPool 采用无界队列 LinkedBlockingQueue,newCachedPool 采用 SynchronousQueue
    • 超时时间不同。newFixedThreadPool 的超时时间默认不生效,因为除非设置 setAllowCoreThreadTimeOut = true,否则核心线程不会被回收。newCachedPool 默认超时时间 60s,适用于任务处理时间很快的场景,占用资源少。
  8. 子线程抛出的异常,主线程能感知到吗?任务执行过程中发生异常,怎么处理?

    感知不到,在异步执行过程中,子线程抛出的异常应该由子线程自己处理,而不是需要主线程感知来协调处理。

    我们可以使用 Guava ThreadFactoryBuilder 指定 UncaughtExceptionHandler(thread, throwable) 这样就可以捕获子线程异常。


    如果某个任务执行出现异常,那么执行任务的线程会被关闭而不是接着执行其他的任务。然后会启动一个新的任务替代它。

    线程池有 execute、submit 两种提交任务方式,最终都会执行到 ThreadPoolExecutor.runWorker() 方法。而 runWorker 的 task.run() 方法最终会调用到 FutureTask.run() 方法,此方法会将业务方法的异常捕获到,然后调用 setException(ex) 方法。

    public void run() {
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable;
                if (c != null && state == NEW) {
                    V result;
                    boolean ran;
                    try {
                        result = c.call();
                        ran = true;
                    } catch (Throwable ex) {
                        result = null;
                        ran = false;
                        setException(ex);
                    }
                    if (ran)
                        set(result);
                }
            } finally {
                runner = null;
                int s = state;
                if (s >= INTERRUPTING)
                    handlePossibleCancellationInterrupt(s);
            }
        }
    
    protected void setException(Throwable t) {
            if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
                outcome = t; // 异常信息被保存到 outcome Object 对象中
                UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
                finishCompletion();
            }
        }
    

    结论:对于线程池的异常处理有三种处理方式:

    1. 业务代码内使用 try/catch 捕获异常信息(最推荐)
    2. 构造线程池时指定 UncaughtExceptionHandler 重写 uncaughtException(thread, throwable) 方法处理子线程异常,通常打印日志
    3. 重写 ThreadPoolExecutor.runWorker 的 afterExecute(task, thrown) 钩子方法,感知异常细节
  9. 什么时候会执行拒绝策略?

    1. 线程池状态大于等于 SHUTDOWN,移除已入队的任务,并执行拒绝策略。
    2. 线程池在运行中,但线程数达到最大线程数 maximumPoolSize,执行拒绝策略。
  10. 任务执行结果是怎么获取的?流程是怎样的?

    通过线程池 submit 方法提交的任务,调用 Future 的 get 方法获取执行结果,建议使用带超时参数的 get 方法。最终会调用 FutureTask.get 方法

    // FutureTask.java 198
    public V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
            if (unit == null)
                throw new NullPointerException();
            int s = state;
            if (s <= COMPLETING && // 超时等待任务完成
                (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
                throw new TimeoutException();
            return report(s); // 获取任务执行结果或异常信息
        }
    // FutureTask.java 116
    private V report(int s) throws ExecutionException {
            Object x = outcome; // 将 outCome 保存的结果或者异常信息返回
            if (s == NORMAL)
                return (V)x;
            if (s >= CANCELLED)
                throw new CancellationException();
            throw new ExecutionException((Throwable)x);
        }