Java Executor源码解析(4)—ThreadPoolExecutor线程池submit方法以及FutureTask源码【一万字】

详细介绍了ThreadPoolExecutor线程池的submit方法的源码,以及FutureTask的原理。

上一篇文章我们介绍了ThreadPoolExecutor线程池的execute方法源码:Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】除了execute方法之外,ThreadPoolExecutor 还有其他三个submit提交的方法,它们的内部都是调用的execute方法,它会返回一个FutureTask,可以从该对象中获取结果或者异常信息,下面来看看它的原理

系列文章:

  1. Java Executor源码解析(1)—Executor执行框架的概述
  2. Java Executor源码解析(2)—ThreadPoolExecutor线程池的介绍和基本属性【一万字】
  3. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】
  4. Java Executor源码解析(4)—ThreadPoolExecutor线程池submit方法以及FutureTask源码【一万字】
  5. Java Executor源码解析(5)—ThreadPoolExecutor线程池其他方法的源码
  6. Java Executor源码解析(6)—ScheduledThreadPoolExecutor调度线程池源码解析【一万字】
  7. Java Executor源码解析(7)—Executors线程池工厂以及四大内置线程池

1 submit方法

1.1 Future submit(Runnable)

public Future< ? > submit(Runnable task)

task要求一个 Runnable 实现类,返回一个 Future 对象。这个 Future 对象可以用来检查 Runnable 是否已经执行完毕。该 Future 的 get 方法在任务成功完成时将会返回 null。

如果任务无法安排执行,那么可能抛出RejectedExecutionException。如果该任务为 null,那么抛出NullPointerException。

/**
 * 位于父类AbstractExecutorService中的实现
 *
 * @throws RejectedExecutionException 如果任务无法安排执行
 * @throws NullPointerException       如果该任务为 null
 */
public Future<?> submit(Runnable task) {
    //首先就是task任务的null校验,如果为null直接抛出NullPointerException
    if (task == null) throw new NullPointerException();
    //将Runnable类型的task任务包装为RunnableFuture类型的ftask任务,返回null
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    //调用execute方法传递ftask,RunnableFuture是Runnable的子接口,因此可以传递
    execute(ftask);
    return ftask;
}

/**
 * 位于父类AbstractExecutorService中的实现
 * 为给定可运行任务返回一个 RunnableFuture。
 * 该RunnableFuture具有默认的返回值,并为底层任务提供取消操作。
 *
 * @param runnable 被包装的任务
 * @param value    将要返回的默认值
 * @param <T>      返回值类型
 * @return 为给定可运行任务返回一个 RunnableFuture。
 */
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    //调用FutureTask的构造器
    return new FutureTask<T>(runnable, value);
}

后面我们再介绍FutureTask是如何对Runnable以及Callable任务进行包装的,以及FutureTask相关方法的实现。

1.2 Future submit(Runnable, T)

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

task要求一个 Runnable 实现类,并且需要一个给定的返回值result,将返回一个 Future 对象,这个 Future 对象除了可以用来检查 Runnable 是否已经执行完毕,还可以调用get()方法获取执行结果,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

如果任务无法安排执行,那么可能抛出RejectedExecutionException。如果该任务为 null,那么抛出NullPointerException。

/**
 * 位于父类AbstractExecutorService中的实现
 * 为给定可运行任务返回一个 RunnableFuture。该RunnableFuture具有指定默认的返回值result,并为底层任务提供取消等操作。
 * 同时调用execute执行这个RunnableFuture实例。
 *
 * @throws RejectedExecutionException 如果任务无法安排执行
 * @throws NullPointerException       如果该任务为 null
 */
public <T> Future<T> submit(Runnable task, T result) {
    //首先就是task任务的null校验,如果为null直接抛出NullPointerException
    if (task == null) throw new NullPointerException();
    //将Runnable类型的task任务包装为RunnableFuture类型的ftask任务,get返回指定值
    RunnableFuture<T> ftask = newTaskFor(task, result);
    //调用execute方法传递ftask,RunnableFuture是Runnable的子接口,因此可以传递
    execute(ftask);
    return ftask;
}

1.3 Future submit(Callable< T >)

public < T > Future< T > submit(Callable< T > task)

task要求一个 Callable实现类。返回一个 Future 对象,这个 Future 对象除了可以用来检查 Runnable 是否已经执行完毕,还可以调用get()方法获取Callable中call方法的执行结果,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

如果任务无法安排执行,那么可能抛出RejectedExecutionException。如果该任务为 null,那么抛出NullPointerException。

/**
 * 位于父类AbstractExecutorService中的实现
 * 为给定可运行任务返回一个 RunnableFuture。该RunnableFuture的返回值result就是传递的Callable的call方法的返回值,并为底层任务提供取消等操作。
 * 同时调用execute执行这个RunnableFuture实例。
 *
 * @throws RejectedExecutionException 如果任务无法安排执行
 * @throws NullPointerException       如果该任务为 null
 */
public <T> Future<T> submit(Callable<T> task) {
    //首先就是task任务的null校验,如果为null直接抛出NullPointerException
    if (task == null) throw new NullPointerException();
    //将Runnable类型的task任务包装为RunnableFuture类型的ftask任务,get返回指定值
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

/**
 * 为给定可运行任务返回一个 RunnableFuture。该RunnableFuture的返回值result就是传递的Callable的call方法的返回值,并为底层任务提供取消等操作。
 *
 * @param callable 被包装的任务
 * @param <T>      Callable返回值类型
 * @return 为给定可运行任务返回一个 RunnableFuture。
 */
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    //调用FutureTask的构造器
    return new FutureTask<T>(callable);
}

2 FutureTask的原理

2.1 FutureTask的概述

public class FutureTask< V >
extends Object
implements RunnableFuture< V >

在最开始我们就简单介绍过,FutureTask作为可取消的异步计算。类提供了对 Future 的基本实现,具有开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法等方法。重要的get()方法仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。

FutureTask可以与线程池连用,也可以单独与Thread使用,因此完全可以与线程池独立开来讲解。

在这里插入图片描述

上面的submit方法内部调用了execute方法,传递的参数是一个FutureTask实例,类型为RunnableFuture,RunnableFuture是Runnable的子接口,因此可以传递。

工作线程在runWorker方法中调用task.run()方法执行任务。此时我们可以知道,对于submit系列方法,并不是直接运行传递的task的run或者call方法,而是运行该task的包装对象FutureTask的run方法。 可以猜测到FutureTask的run方法对于传递进来的原始的Runnable或者Callable任务的run或者call方法做了包装。现在我们来看看FutureTask到底是如何对Runnable或者Callable任务进行包装的。

先看一个简单的使用案例:

FutureTask objectFutureTask = new FutureTask(() -> {
    Thread.sleep(3000);
    return 1;
});
//与线程连用
Thread thread = new Thread(objectFutureTask);
thread.start();
//约3秒钟之后会获取返回值
System.out.println(objectFutureTask.get());

2.2 FutureTask的重要属性

FutureTask作为一个任务,具有7种不同的运行状态,每种运行状态使用单独的int类型表示,并且它们有大小关系,这类似于ThreadPoolExecutor的状态的作用,可以通过比较大小来判断任务执行情况:NEW< COMPLETING< NORMAL< EXCEPTIONAL< CANCELLED< INTERRUPTING< INTERRUPTED。比如get方法就用到了大小关系。

内部保存的任务类型只有一个Callable类型,也就是说,实际上构造器传递进来的Runnable任务也会通过Executors.callable(runnable, result)工厂方法转换为Callable类型的任务。

具有一个Object类型的outcome保存要从get方法返回的数据或者要抛出的异常。

还有一个WaitNode类型的单链表,用于保存等待该FutureTask的返回值的线程。

/**
 * 此任务的运行状态
 * 运行状态仅在set、setException、cancel方法中转换
 * 可能的状态转换如下:
 * NEW -> COMPLETING -> NORMAL  表示正常执行完毕
 * NEW -> COMPLETING -> EXCEPTIONAL     表示执行任务时发生异常
 * NEW -> CANCELLED     表示任务被取消,cancel(false)。如果此任务尚未启动,则此任务将永不运行;如果任务已经启动,则不影响运行
 * NEW -> INTERRUPTING -> INTERRUPTED   表示任务被取消,cancel(false)。如果此任务尚未启动,则此任务将永不运行;如果任务已经启动,则停止执行(中断任务线程)
 */
private volatile int state;
/**
 * new新建任务,或者正在执行任务call()方法的状态
 */
private static final int NEW = 0;
/**
 * 任务call()方法被调用完毕之后(无论成功还是抛出异常)的状态,
 * 或者调用set、setException方法CAS成功之后的状态
 * 此时outcome还没有记录执行结果
 * 这是一个中间状态
 */
private static final int COMPLETING = 1;
/**
 * 任务call()方法被调用成功之后的最终状态
 * 或者调用set方法直接设置结果成功之后的最终状态
 * 此时outcome已经记录执行结果
 * 该状态不需要立即感知
 */
private static final int NORMAL = 2;
/**
 * 任务call()方法被调用抛出异常之后的最终状态
 * 或者调用setException方法直接设置异常信息成功之后的最终状态
 * 此时outcome已经记录执行结果
 * 该状态不需要立即感知
 */
private static final int EXCEPTIONAL = 3;
/**
 * 任务还未执行或者正在执行(NEW状态)时,调用cancel(false)尝试取消任务操作成功的最终状态
 */
private static final int CANCELLED = 4;
/**
 * 任务还未执行或者正在执行(NEW状态)时,调用cancel(true)尝试取消任务操作成功的状态
 * 这是一个中间状态
 */
private static final int INTERRUPTING = 5;
/**
 * 调用cancel(true)设置INTERRUPTING状态成功之后会继续设置的最终状态
 * 如果此时任务正在执行,那么中断该任务运行线程之后设置的状态
 * 该状态不需要立即感知
 */
private static final int INTERRUPTED = 6;

/**
 * 保存的构造器传递进来的任务,可以看到就是Callable类型
 * 实际上构造器传递进来的Runnable任务也会通过Executors.callable(runnable, result)工厂方法转换为Callable类型的任务
 * 在运行完毕之后(无论是成功还是异常)将会置为null
 */
private Callable<V> callable;
/**
 * 要返回的结果,或者要从get()方法中抛出的异常
 * 默认为null,set或者setException方法调用成功之后被设置值
 */
private Object outcome;
/**
 * 运行Callable任务的线程
 * 默认为null,在run方法被成功调用之后会被赋值初始化
 */
private volatile Thread runner;
/**
 * 等待该FutureTask的返回值的线程,是一个单链表队列,waiters存储头部元素
 */
private volatile WaitNode waiters;

/**
 * 简单的单链表实现内部类,用于存放在get方法上等待的线程
 */
static final class WaitNode {
    /**
     * 等待的线程
     */
    volatile Thread thread;
    /**
     * 后继
     */
    volatile WaitNode next;

    /**
     * 构造器
     */
    WaitNode() {
        thread = Thread.currentThread();
    }
}

2.3 FutureTask的构造器

public FutureTask(Callable< V > callable)

创建一个 FutureTask,一旦运行就执行给定的Callable任务,并且任务完成时可通过get方法返回call()的结果。

public FutureTask(Runnable runnable, V result)

创建一个 FutureTask,一旦运行就执行给定的Runnable任务,并且任务完成时可通过get方法返回指定的结果。

sumbit方法就是通过调用FutureTask的构造器返回FutureTask实例的。可以看到传递进来的Runnable任务也会通过Executors.callable(runnable, result)工厂方法转换为Callable类型的任务,这是一种设计模式—适配器模式。而FutureTask对callable任务的包装也是一种适配器模式——转换为Runnable类型。

构造器仅仅是初始化callable属性,以及FutureTask状态为NEW。

/**
 * 创建一个 FutureTask,一旦运行就执行给定的Callable任务,并且任务完成时可通过get方法返回call()的结果。
 *
 * @param callable callable任务
 * @throws NullPointerException 如果 callable 为 null。
 */
public FutureTask(Callable<V> callable) {
    //callable的null校验
    if (callable == null)
        throw new NullPointerException();
    //初始化callable属性
    this.callable = callable;
    //初始化FutureTask状态为NEW
    this.state = NEW;       // ensure visibility of callable
}

/**
 * 创建一个 FutureTask,一旦运行就执行给定的Runnable任务,并且任务完成时可通过get方法返回指定的结果。
 *
 * @param runnable runnable 任务
 * @param result   任务完成时指定返回的结果
 * @throws NullPointerException 如果 runnable 为 null。
 */
public FutureTask(Runnable runnable, V result) {
    //调用Executors.callable将runnable和指定返回值包装并返回一个callable
    //这里的源码我们在 Executors章节部分讲解
    this.callable = Executors.callable(runnable, result);
    //初始化FutureTask状态为NEW
    this.state = NEW;       // ensure visibility of callable
}

2.4 run核心方法

无论是单独和一个Thread线程使用还是和线程池使用,内部的线程运行的不再是被包装的任务的run或者call方法,而是FutureTask的run。这个方法是FutureTask的核心方法,只能被执行任务的线程调用,其他线程是无法调用的。

run的详细步骤如下,看的时候建议结合更详细的代码注释一起看:

  1. 校验任务状态state: 如果如果state不是NEW状态,或者state是NEW状态,但是CAS的将runner从null设置为当前调用线程失败。以上两个条件满足一个就立即返回,任务不会被执行,即要求任务在此前没有被执行过才能开始执行。
  2. 到这一步,说明任务没有被执行过,此时执行该任务的线程被记录在runner中。在cancel(true)方法中,如果runner不为null,那么任务就被算作正在执行过程中了,开启一个try块,执行任务:
    1. 保存要执行的任务c,校验任务: 如果c不为null,并且state还是为NEW,那么可以执行真正的任务。这里还需要校验一下state,因为在上次校验到此之间可能存在其他线程取消了任务,如果任务在真正开始前就被取消了,那么直接就不执行真正的任务。
      1. result变量用来保存执行结果,ran变量用来保存任务是否执行成功。
      2. 在一个try块中,调用c.call()方法,这里才是执行真正的任务,这个call方法中的代码就是我们自己编写的代码。如果call执行成功,那么result接收其返回值,ran置为true,表示执行成功。
      3. 使用catch捕获执行过程中的任何异常,如果call方法抛出异常,那么result置为null,ran置为false,表示执行失败。随后调用setException方法,设置异常的返回值,并唤醒因为get阻塞的线程。
      4. 如果ran为true,即任务执行成功。调用set方法,用于设置正常的返回值,并唤醒因为get阻塞的线程。
  3. 任务执行完毕或者没有执行,都会进入finally代码块中:
    1. 将runner设置为null,到此表示任务执行完毕。在任务的校验以及执行过程中runner必须一直非null,用来保证run()方法不会被并发的调用。
    2. 重新获取此时的state状态s,因为在上面的任务的校验以及执行过程中可能被cancel方法取消,state会被改变。
    3. 如果状态s大于等于INTERRUPTING,表示任务执行过程中其他线程执行了cancel(true)成功,那么本次任务执行的setException以及set方法都执行失败,但是此执行线程不一定被中断了,任务也不一定被取消了。可能执行线程在执行任务call()方法完毕时,cancel的线程更改了state值为INTERRUPTING,这将导致执行线程的setException或者set失败,随后执行线程将runner置空,那么cancel线程在后续代码中由于runner为null将不会中断执行线程;但是也有可能cancel线程先获取到了runner,此时runner还不为null,但是执行线程实际上将任务执行完了,此时还是会中断线程。
      1. 此时调用handlePossibleCancellationInterrupt处理可能的中断,等待任务状态变成INTERRUPTED状态,即等待执行线程被中断或者不被中断。
/**
 * FutureTask的核心方法,被执行线程调用,用来执行任务
 */
public void run() {
    /*
     * 如果state不是NEW状态
     * 或者 state是NEW状态,但是CAS的将runner从null设置为当前调用线程失败
     *
     * 以上两个条件满足一个就立即返回,任务不会被执行,即要求任务在此前没有被执行过才能开始执行
     */
    if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                    null, Thread.currentThread()))
        return;
    /*
     * 到这一步,说明任务没有被执行过,此时执行该任务的线程被记录在runner中
     * 在cancel(true)方法中,如果runner不为null,那么任务就被算作正在执行过程中了
     * 开启一个try块,执行任务
     */
    try {
        //保存要执行的任务c
        Callable<V> c = callable;
        //如果c不为null,并且state还是为NEW,那么可以执行真正的任务
        //这里还需要校验一下,因为在上次校验到此之间可能存在其他线程取消了任务
        //如果被取消了,那么不会执行底层真正的任务
        if (c != null && state == NEW) {
            //result用来保存执行结果
            V result;
            //ran用来保存任务是否执行成功
            boolean ran;
            try {
                //调用c.call()方法,这里才是执行真正的任务,这个call方法中的代码就是我们自己编写的代码
                //如果call执行成功,那么result接收其返回值
                result = c.call();
                //ran置为true,表示执行成功
                ran = true;
            } catch (Throwable ex) {
                //如果call方法抛出异常,那么result置为null
                result = null;
                //ran置为false,表示执行失败
                ran = false;
                //调用setException方法,用于设置异常的返回值,并唤醒因为get阻塞的线程
                setException(ex);
            }
            //如果ran为true,即任务执行成功
            if (ran)
                //调用set方法,用于设置正常的返回值,并唤醒因为get阻塞的线程
                set(result);
        }
    } finally {
        //执行完毕后在finally中,将runner设置为null,到此表示任务执行完毕
        //在任务执行过程中runner必须一直非null,用来保证run()方法不会被并发的调用
        runner = null;
        //重新获取此时的state,因为在任务执行过程中可能被中断,state会被改变
        int s = state;
        /*
         * 如果状态大于等于INTERRUPTING,表示任务执行过程中其他线程执行了cancel(true)成功
         * 那么本次任务执行的setException以及set方法都执行失败,但是此执行线程不一定被中断了
         * 可能执行线程在执行任务call()方法完毕时,cancel的线程更改了state值为INTERRUPTING,这将导致执行线程的setException或者set失败
         *
         * 随后执行线程将runner置空,那么cancel线程在后续代码中由于runner为null将不会中断执行线程
         * 但是也有可能cancel线程先获取到了runner,此时runner还不为null,但是执行线程实际上将任务执行完了,此时还是会中断线程
         */
        if (s >= INTERRUPTING)
            //处理中断,等待任务状态变成INTERRUPTED状态,即等待执行线程被中断或者不被中断
            handlePossibleCancellationInterrupt(s);
    }
}

/**
 * 处理由于调用cancel(true)方法而在任务执行过程中可能的中断
 * 等待任务状态变成INTERRUPTED状态,即等待执行线程被中断或者不被中断
 *
 * @param s 状态
 */
private void handlePossibleCancellationInterrupt(int s) {
    //如果状态为INTERRUPTING
    if (s == INTERRUPTING)
        //那么循环或去哦最新的状态,如果等于INTERRUPTING,那么让出cpu执行权
        //让cancel(true)的线程尽快将状态设置为INTERRUPTED成功
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt

}

2.4.1 set、setException设置结果

run方法中的c.call()调用成功返回或者抛出异常的时候,将分别调用set和setException方法用于尝试CAS的设置state状态为COMPLETING,设置成功之后会继续设置get方法的返回结果outcome,接着lazyset的设置最终状态NORMAL或者EXCEPTIONAL,最后会调用finishCompletion方法唤醒因为调用get方法而阻塞在内部等待队列中的外部线程,并且最后清理callable任务引用。

/**
 * 除非已经设置了此 Future已不是NEW状态,否则将其get方法的结果设置为给定的值。
 * 在计算成功完成时通过 run 方法内部调用此方法。
 *
 * @param v c.call方法的返回值
 */
protected void set(V v) {
    //尝试CAS的将state的值从NEW设置为COMPLETING,失败则退出方法
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        //CAS成功之后,就是所谓的COMPLETING状态
        //随后将v的值赋给outcome,作为get方法的返回值
        outcome = v;
        //设置当前任务的状态为NORMAL ,也就是任务正常结束
        /*
         * 最终会设置成NORMAL状态,但是可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
         * 关于该方法的更多信息可以参考并发编程网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》,
         * 文章地址是“http://ifeve.com/how-does-atomiclong-lazyset-work/”。
         * AtomicLong.lazySet方法的内部就是调用的putOrdered系列方法
         *
         * 这里没有使用CAS 是因为对于同一个任务只可能有一个线程运行到这里。
         * 在这里使用putOrderedlnt,比使用CAS或者putLongvolatile的效率要高,
         * 并且这里的场景不要求其他线程马上对设置的NORMAL状态值可见。
         */
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        //调用finishCompletion方法,唤醒因为调用get方法而阻塞在内部等待队列中的外部线程
        //并且最后清理callable任务引用。
        finishCompletion();
    }
}

/**
 * 除非已经设置了此 Future已不是NEW状态,否则将其get方法的结果设置为抛出一个ExecutionException,并将给定的 throwable 作为其原因。
 * 在计算失败时通过 run 方法内部调用此方法。
 *
 * @param t c.call方法异常的原因
 */
protected void setException(Throwable t) {
    //尝试CAS的将state的值从NEW设置为COMPLETING,失败则退出方法
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        //CAS成功之后,就是所谓的COMPLETING状态
        //随后将t的值赋给outcome,作为get方法要抛出的异常
        outcome = t;
        /*
         * 最终会设置成NORMAL状态,但是可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
         * 关于该方法的更多信息可以参考并发编程网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》,
         * 文章地址是“http://ifeve.com/how-does-atomiclong-lazyset-work/”。
         * AtomicLong.lazySet方法的内部就是调用的putOrdered系列方法
         *
         * 这里没有使用CAS 是因为对于同一个任务只可能有一个线程运行到这里。
         * 在这里使用putOrderedlnt,比使用CAS或者putLongvolatile的效率要高,
         * 并且这里的场景不要求其他线程马上对设置的EXCEPTIONAL状态值可见。
         */
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        //调用finishCompletion方法,唤醒因为调用get方法而阻塞在内部等待队列中的外部线程
        //并且最后清理callable任务引用。
        finishCompletion();
    }
}

2.5 cancel取消任务

public boolean cancel(boolean mayInterruptIfRunning)

试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。

当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程,如果为true,表示应该中断执行此任务的线程,如果为false,表示允许正在运行的任务运行完成。

如果无法取消任务,则返回 false,表示它已经正常完成或者已被取消;否则返回 true,返回true也不代表任务方法没有执行完毕,有可能任务执行完了,只是不能获取返回值而已。因为中断线程不代表会中断任务,即使中断了线程,只是设置了中断标志位,任务有可能实际上还是执行完毕了的,只不过无法获得返回值(get抛出异常)。Java线程中断与停止线程详解以及案例演示

cancel的详细步骤如下,看的时候建议结合更详细的代码注释一起看:

  1. 任务状态state校验: 如果state不等于NEW,说明该任务已完成或者取消;或者如果state等于NEW,但是尝试将CAS的将state的值从NEW改为INTERRUPTING(mayInterruptIfRunning为true)或者CANCELLED(mayInterruptIfRunning为false)失败,表示状态改变失败。这两种情况都表示取消任务失败,直接返回false。
  2. 到这一步,表示任务状态state已经被改为INTERRUPTING或者CANCELLED,此时如果在run方法中c.call方法运行完毕或者抛出异常,后续的set或者setException方法也不会成功。而如果run方法中的任务还没有开始执行,那么后面也就不会执行了。开启一个try代码块:
    1. 如果mayInterruptIfRunning为true,说明需要中断正在执行的线程。开启一个try代码块:
      1. 使用t记录此时的runner,注意这里只是记录了瞬时的runner的状态,有可能刚把runner记录下来,runner在run方法中就被置为null了。
      2. 如果t不为null,那么将t中断。
    2. 在finally代码块中*,会将状态lazySet的设置为INTERRUPTED状态。
  3. 在finally代码块中,调用finishCompletion方法,唤醒因为调用get方法而阻塞在内部等待队列中的外部线程,并且最后清理callable任务,即callable不能被再次调用。
  4. 最后返回true。

可以看到,cancel方法就有两条任务状态转换路线:

cancel(false): NEW——CANCELLED
cancel(true): NEW——INTERRUPTING——INTERRUPTED

/**
 1. 尝试取消任务的执行
 2.  3. @param mayInterruptIfRunning 如果应该中断正在执行此任务的线程,则为 true;否则允许正在运行的任务运行完成
 4. @return 如果无法取消任务,则返回 false,这通常是由于它已经正常完成或者已取消;否则返回 true
 5. 返回true也不代表任务方法没有执行完毕,有可能任务执行完了,只是不能获取返回值而已。
 */
public boolean cancel(boolean mayInterruptIfRunning) {
    /*
     * 1 如果state不等于NEW,说明该任务已完成或者取消
     * 2 如果state等于NEW,但是尝试将CAS的将state的值从NEW改为INTERRUPTING(mayInterruptIfRunning为true)
     *   或者CANCELLED(mayInterruptIfRunning为false)失败,表示状态改变失败
     *
     * 这两种情况都表示取消任务失败,直接返回false
     */
    if (!(state == NEW &&
            UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                    mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    /*
     * 到这一步,表示任务状态state已经被改为INTERRUPTING或者CANCELLED,
     * 此时如果在run方法中c.call方法运行完毕或者抛出异常,后续的set或者setException方法也不会成功
     * 而如果run方法中的任务还没有开始执行,那么后面也就不会执行了。
     */
    try {    // in case call to interrupt throws exception
        //如果mayInterruptIfRunning为true,说明需要中断正在执行的线程
        if (mayInterruptIfRunning) {
            try {
                //使用t记录此时的runner,注意这里只是记录了瞬时的runner的状态,有可能刚把runner记录下来,runner在run方法中就被置为null了
                Thread t = runner;
                /*
                 * 如果t不为null,那么将t中断。可以看到,所谓的尝试停止任务仅仅是尝试中断执行任务的线程
                 * 如果任务线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long,int)、
                 * sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException,这是才可能中断任务执行的可能
                 * 而这个interrupt()不会对正常运行的线程有任何影响,仅仅是设置了中断标志位为true,因此不一定会中断任务执行
                 */
                if (t != null)
                    t.interrupt();
            } finally { // final state
                /*
                 * 最终会设置成INTERRUPTED状态,但是可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
                 * 关于该方法的更多信息可以参考并发编程网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》,
                 * 文章地址是“http://ifeve.com/how-does-atomiclong-lazyset-work/”。
                 * AtomicLong.lazySet方法的内部就是调用的putOrdered系列方法
                 *
                 * 这里没有使用CAS 是因为对于同一个任务只可能有一个线程运行到这里。
                 * 在这里使用putOrderedlnt,比使用CAS或者putLongvolatile的效率要高,
                 * 并且这里的场景不要求其他线程马上对设置的INTERRUPTED状态值可见。
                 */
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        //调用finishCompletion方法,唤醒因为调用get方法而阻塞在内部等待队列中的外部线程
        //并且最后清理callable任务引用。
        finishCompletion();
    }
    //返回true
    return true;
}

2.6 finishCompletion唤醒等待线程

在set、setException、cancel方法的最后都会调用finishCompletion方法,这三个方法啊实际上都表示任务执行完成的状态,即正常执行、异常执行、取消执行,任务完成之后调用的finishCompletion方法就是为了唤醒因为调用get方法而阻塞在内部等待队列中的外部线程,并且最后清理callable任务引用。

大概步骤还是很简单的:

  1. 开启一个循环,q=waiter,如果q不为null,说明有等待的线程,那么进入循环:
    1. 尝试CAS的将waiter的指向从q变成null,即清除waiter的引用,好让GC回收,CAS成功之后:
      1. 再开启一个内层死循环,用于清理q队列:
        1. 获取q保存的阻塞的线程t;
        2. 如果t不为null,那么将q.thread引用置为null,unpark唤醒阻塞的t线程。
        3. 获取q的后继next,如果后继next为null,说明遍历到了队列尾部,那么break跳出内层循环。
        4. 到这一步还没有跳出,表示next不为null,那么q的后继设置为null,这样q结点就脱离了队列。
        5. q设置为q的后继next,继续下一次循环,即继续向后推进移除结点唤醒线程;
      2. 跳出内层循环之后,继续break跳出外层循环;
    2. CAS失败则继续下一次外层循环;
  2. 执行done方法,该方法是一个空实现,留给子类重写;
  3. callable设置为null,释放引用。

关于线程的park阻塞和unpark唤醒,我们以前已经讲过了,它不同于锁,原理还是比较复杂的,涉及到native方法,详见这篇文章:JUC—LockSupport以及park、unpark方法底层源码深度解析

/**
 * 唤醒因为调用get方法而阻塞在内部等待队列中的外部线程,并且最后清理callable任务,即callable不能被再次调用。
 * 在set、setException、cancel方法的最后都会调用
 */
private void finishCompletion() {
    // assert state > COMPLETING;
    /*
     * 开启一个循环
     * q=waiter,如果q不为null,说明有等待的线程,那么进入循环
     */
    for (WaitNode q; (q = waiters) != null; ) {
        //尝试CAS的将waiter的指向从q变成null,即清除这个引用
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            /*CAS成功之后,开启一个死循环清理q队列*/
            for (; ; ) {
                //获取q保存的阻塞的线程t
                Thread t = q.thread;
                //如果t不为null
                if (t != null) {
                    //那么将q.thread引用置为null
                    q.thread = null;
                    //unpark唤醒阻塞的t线程
                    LockSupport.unpark(t);
                }
                //获取q的后继next
                WaitNode next = q.next;
                //如果后继next为null,说明遍历到了队列尾部,那么break跳出内层循环
                if (next == null)
                    break;
                //到这一步表示next不为null,那么q的后继设置为null,这样q结点就脱离了队列
                q.next = null; // unlink to help gc
                //q设置为q的后继next,继续下一次循环,即继续向后推进移除结点唤醒线程
                q = next;
            }
            //跳出内层循环之后,继续break跳出外层循环
            break;
        }
    }
    //执行done方法,该方法是一个空实现,留给子类重写
    done();
    //callable设置为null,释放引用
    callable = null;        // to reduce footprint
}

2.7 get()等待获取结果

public V get()

获取任务执行的结果。如果任务没有执行完毕,那么一直等待直到等待被中断,或者被唤醒。

如果任务被取消,那么抛出CancellationException;如果当前的线程在等待时被中断,那么抛出InterruptedException;如果任务执行时抛出异常,那么抛出ExecutionException。

大概步骤就是:

  1. 获取state状态值s;
  2. 如果s的值小于等于COMPLETING,说明任务还没开始执行,或者正在执行,或者执行完毕但是还没有设置好返回值,那么需要等待:
  3. 调用awaitDone方法传递false、0,表示无限等待,返回任务的状态s。
  4. 调用report方法解析状态值s,并且做出相应的响应。
/**
 1. 获取任务执行的结果。如果任务没有执行完毕,那么一直等待直到被中断。
 2.  3. @throws CancellationException 如果任务被取消,那么抛出CancellationException
 4. @throws InterruptedException  如果当前的线程在等待时被中断
 5. @throws ExecutionException    如果任务执行时抛出异常
 */
public V get() throws InterruptedException, ExecutionException {
    //获取state状态值s
    int s = state;
    //如果s的值小于等于COMPLETING,说明任务还没开始执行,或者正在执行,或者执行完毕但是还没有设置好返回值
    //那么需要等待
    if (s <= COMPLETING)
        //调用awaitDone,传递false、0,表示无限等待,返回任务的状态
        s = awaitDone(false, 0L);
    //调用report方法解析状态值s,并且做出相应的响应
    return report(s);
}

2.7.1 awaitDone等待结果

awaitDone用于等待最终结果,直到任务完毕正常返回(没有阻塞),或者阻塞被unpark唤醒,或者阻塞被中断,或者阻塞超时。
最终会返回一个任务状态,如果当前的线程阻塞被中断则直接抛出InterruptedException异常。

awaitDone的详细步骤如下,看的时候建议结合更详细的代码注释一起看:

  1. 如果是超时等待,那么计算超时时间点纳秒,否则为0,使用deadline保存结果;
  2. q保存加入到waiters的结点,或者为null表示没有加入,q将会被加入到链表头部;
  3. queued代表q结点是否已加入链表的标志,默认为false,表示未加入;
  4. 开启一个死循环等待,直到任务执行完毕,或者超时时间到,或者等待被中断:
    1. 如果当前线程被中断了,那么清除中断标志位,随后进入if代码块:
      1. 从waiters移除q结点;随后抛出InterruptedException异常。
    2. 获取此时的任务状态s。如果s大于COMPLETING,表示任务已完成:
      1. 如果q不等于null,那么q的thread置为null,表示不需要在finishCompletion中被唤醒,并且表示q结点是一个无效结点。
      2. 返回状态s。这里能够看出来,q结点没有被即时清理。waiters链表中结点的thread如果为null,那么表示这个结点是无效结点,可以被随时清理。因此无效结点将会在removeWaiter或者finishCompletion方法中被一起清理了。
    3. 否则,表示s小于等于COMPLETING。如果s等于COMPLETING,那么表示任务已完成,但是返回值或者最终状态还没有写入:
      1. 这里就不需要阻塞了,直接尝试yield让出CPU执行权,希望run线程早点执行完毕。
    4. 否则,表示s小于COMPLETING,那就是任务还没有执行或者执行过程中,这样就还不知道任务什么时候执行或者执行完,此时可能需要将线程包装为WaitNode结点加入链表等待。如果q为null:
      1. 那么新建一个WaitNode,thread属性在构造器中已经计算出来了。注意这里并没有直接加入链表,而是继续下一次循环,期待下一次循环就能返回或者yield也行,尽量避免阻塞的调用。
    5. 否则,到这里表示在前面的判断中没有返回或者yield,并且q不为null。如果queued为false,即q结点还没有加入链表:
      1. 那么将q的后继设置为此时的waiters结点,随后尝试CAS的将waiters的指向从q的后继指向q。简单的说就是CAS的加入链表成为链表头部元素,并且waiters持有该结点。注意这里加入之后没有直接阻塞,而是继续下一次循环,期待下一次循环就能返回或者yield也行,尽量避免阻塞的调用。
    6. 否则,到这里在前面的判断中没有返回或者yield,并且q不为null,并且已经加入了链表,此时可以尝试阻塞了。如果timed为true,即是超时操作:
      1. 那么计算剩余超时时间纳秒nanos;
      2. 如果nanos小于等于0,表示超时时间到了也没有获取到任务的结果,那么从链表中移除q结点,返回此时的state,不管是什么状态。
      3. 如果nanos大于0,那么调用parkNanos方法阻塞当前线程最长nanos纳秒,直到被唤醒,或中断,或阻塞时间到,随后继续下一次循环。注意parkNanos被中断时不会主动抛出InterruptedException异常,因此还需要在下一次循环中手动判断中断标志位。
    7. 到这里,表示非超时操作。
      1. 调用park方法一直阻塞当前线程,直到被唤醒,或中断,随后继续下一次循环。注意park被中断时不会主动抛出InterruptedException异常,因此还需要在下一次循环中手动判断中断标志位。
    8. 到这里,继续下一次死循环。
/**
 * awaitDone用于等待最终结果,直到任务完毕正常返回(没有阻塞),或者阻塞被unpark唤醒,或者阻塞被中断,或者阻塞超时。
 *
 * @param timed 是否是超时等待
 * @param nanos 超时等待时间纳秒,仅在timed为true时有效
 * @return 返回时任务的状态
 */
private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    //如果是超时等待,那么计算超时时间点纳秒,否则为0,使用deadline保存结果
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    //q保存加入到waiters的结点,或者为null表示没有加入,q将会被加入到链表头部
    WaitNode q = null;
    //queued代表q结点是否已加入链表的标志,默认为false,表示未加入
    boolean queued = false;
    /*循环等待,直到任务执行完毕,或者超时时间到,或者等待被中断*/
    for (; ; ) {
        //如果当前线程被中断了,那么清除中断标志位,随后进入if代码块
        if (Thread.interrupted()) {
            //从waiters移除q结点
            removeWaiter(q);
            //抛出InterruptedException异常
            throw new InterruptedException();
        }
        //获取此时的任务状态s
        int s = state;
        /*如果s大于COMPLETING,表示任务已完成*/
        if (s > COMPLETING) {
            //如果q不等于null
            if (q != null)
                //那么q的thread置为null,表示不需要在finishCompletion中被唤醒,并且表示q结点是一个无效结点
                q.thread = null;
            //返回状态s
            return s;
            //这里能够看出来,q结点没有被即时清理。waiters链表中结点的thread如果为null,那么表示这个结点是无效结点
            //可以被随时清理,因此无效结点将会在removeWaiter或者finishCompletion方法中被一起清理了
        }
        /*
         * 否则,表示s小于等于COMPLETING
         * 如果s等于COMPLETING,那么表示任务已完成,但是返回值或者最终状态还没有写入
         */
        else if (s == COMPLETING) // cannot time out yet
            //这里就不需要阻塞了,直接尝试yield让出CPU执行权,希望run线程早点执行完毕
            Thread.yield();
            /*
             * 否则,表示s小于COMPLETING,那就是任务还没有执行或者执行过程中
             * 还不知道任务什么时候执行或者执行完,此时可能需要将线程包装为WaitNode结点加入链表等待
             *
             * 如果q为null,那么新建一个WaitNode,thread属性在构造器中已经计算出来了
             * 注意这里并没有直接加入链表,而是继续下一次循环,期待下一次循环就能返回或者yield也行,尽量避免阻塞的调用
             */
        else if (q == null)
            q = new WaitNode();
            /*
             * 否则,到这里在前面的判断中没有返回或者yield,并且q不为null
             * 如果queued为false,即q结点还没有加入链表
             *
             * 那么将q的后继设置为此时的waiters结点,随后尝试CAS的将waiters的指向从q的后继指向q
             * 简单的说就是CAS的加入链表成为链表头部元素,并且waiters持有该结点
             *
             * 注意这里加入之后没有直接阻塞,而是继续下一次循环,期待下一次循环就能返回或者yield也行,尽量避免阻塞的调用
             */
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                    q.next = waiters, q);
            /*
             * 否则,到这里表示在前面的判断中没有返回或者yield,并且q不为null,并且已经加入了链表
             * 此时可以尝试阻塞了
             *
             * 如果timed为true,即是超时操作
             */
        else if (timed) {
            //那么计算剩余超时时间纳秒nanos
            nanos = deadline - System.nanoTime();
            //如果nanos小于等于0,表示超时时间到了也没有获取到任务的结果
            if (nanos <= 0L) {
                //从链表中移除q结点
                removeWaiter(q);
                //返回此时的state,不管是什么状态
                return state;
            }
            //如果nanos大于0,那么调用parkNanos方法阻塞当前线程最长nanos纳秒,直到被唤醒,或中断,或阻塞时间到,随后继续下一次循环
            //注意parkNanos被中断时不会主动抛出InterruptedException异常,因此还需要在下一次循环中手动判断中断标志位
            LockSupport.parkNanos(this, nanos);
        }
        /*
         * 到这里,表示非超时操作
         */
        else
            //调用park方法一直阻塞当前线程,直到被唤醒,或中断,随后继续下一次循环
            //注意park被中断时不会主动抛出InterruptedException异常,因此还需要在下一次循环中手动判断中断标志位
            LockSupport.park(this);

        //到这里,继续下一次死循环
    }
}
2.7.1.1 removeWaiter清理无效结点

仅在awaitDone方法中被调用,如果线程等待时被中断或者等待超时,那么将调用removeWaiter方法,尽量移除waiters链表中的全部无效的结点。

原理还是很简单的:如果参数node不为null,那么首先将node的thread置为null,标识为无效结点,随后从头到尾遍历整个waiters链表,将途中遇到的thread为null的结点移除链表即可。

/**
 1. 仅在awaitDone方法中被调用
 2. 如果线程等待时被中断或者等待超时,那么将尽量移除waiters链表中的全部无效的结点
 3.  4. @param node 希望被移除的结点
 */
private void removeWaiter(WaitNode node) {
    //如果node不为null,那么尝试移除
    if (node != null) {
        //将node.thread置为null,释放引用,表明node结点是一个无效结点
        node.thread = null;
        /*开启一个死循环,使用retry标记*/
        retry:
        for (; ; ) {          // restart on removeWaiter race
            /*
             * 内部再开启一个循环,将无效结点全部清理了
             * 初始化条件:pred=null,pred表示q的前驱,q=waiters,q表示当前结点,从链表头开始
             * 循环条件:q不为null的时候,继续循环;设置变量s,作为q的后继
             * 后置条件:q设置为s,即向后推进
             */
            for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                //s保存q的后继
                s = q.next;
                //如果q的thread不为null,表示q是一个有效结点
                if (q.thread != null)
                    //那么pred设置为q,继续下一次循环
                    pred = q;
                    /*
                     * 否则,表示q是一个无效结点,可以清理
                     * 如果pred前驱不为null
                     */
                else if (pred != null) {
                    //pred的后继设置为s,移除这个没用的q结点
                    pred.next = s;
                    //如果pred的thread此时也变成null了,那就没办法了
                    if (pred.thread == null) // check for race
                        //continue retry,结束内层循环,结束本次外层循环,继续下一次外层循环
                        //相当于从头开始清理了
                        continue retry;
                }
                /*
                 * 如果pred前驱为null,说明q就是链表头结点
                 * 那么尝试将waiters指向q的后继s
                 */
                else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
                        q, s))
                    //continue retry,结束内层循环,结束本次外层循环,继续下一次外层循环
                    //相当于从头(q的后继s)开始清理了,
                    continue retry;
            }
            //到这一步,一定是q为null的情况,表示清理到了队列尾部,那么break跳出外层循环,该方法结束
            break;
        }
    }
}

2.7.2 report解析状态

get方法的最后会调用report方法,用来解析获取的任务状态,最终会返回执行结果,或者抛出各种异常。

  1. s = NORMAL:表示任务正常结束,返回执行结果;
  2. s = EXCEPTIONAL:表示任务执行时抛出异常,抛出ExecutionException异常;
  3. s >= CANCELLED:表示任务被取消了(实际上可能已经执行了),抛出CancellationException异常;
/**
 * 解析任务状态,最终会返回执行结果,或者抛出各种异常
 *
 * @param s 任务状态值
 */
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
    //获取执行结果x,可能是返回值或者一个异常
    Object x = outcome;
    //如果s等于NORMAL,即任务正常结束
    if (s == NORMAL)
        //那么返回执行结果
        return (V) x;
    //如果s大于等于CANCELLED,那么表示任务被取消了(实际上可能已经执行了)
    if (s >= CANCELLED)
        //抛出CancellationException异常
        throw new CancellationException();
    //到这一步一定是EXCEPTIONAL状态,表示任务执行时抛出异常,抛出ExecutionException异常
    throw new ExecutionException((Throwable) x);
}

2.8 get (timeout, unit)超时等待获取结果

public V get(long timeout, TimeUnit unit)

获取任务执行的结果。如果任务没有执行完毕,那么超时等待直到等待被中断,或者被唤醒,或者等待超时。

如果任务被取消,那么抛出CancellationException;如果当前的线程在等待时被中断,那么抛出InterruptedException;如果任务执行时抛出异常,那么抛出ExecutionException;如果等待超时,那么抛出TimeoutException。

原理和get()都差不多,只不过多了超时状态的处理,即抛出TimeoutException。

/**
 * 获取任务执行的结果。如果任务没有执行完毕,那么超时等待直到等待被中断,或者被唤醒,或者等待超时。
 *
 * @param timeout 超时时间
 * @param unit    时间单位
 * @return 执行结果
 * @throws CancellationException 如果任务被取消,那么抛出CancellationException
 * @throws InterruptedException  如果当前的线程在等待时被中断
 * @throws ExecutionException    如果任务执行时抛出异常
 * @throws TimeoutException      如果等待超时
 * @throws NullPointerException  如果时间单位unit为null
 */
public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    //如果时间单位为null,那么抛出NullPointerException
    if (unit == null)
        throw new NullPointerException();
    //获取state状态值s
    int s = state;
    //如果s的值小于等于COMPLETING,说明任务还没开始执行,或者正在执行,或者执行完毕但是还没有设置好返回值
    //那么需要等待,调用awaitDone,传递true、unit.toNanos(timeout),表示超时等待,返回任务的状态s
    if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        //如果返回任务的状态s小于等于COMPLETING,那么表示超时时间到了还没有获取结果,抛出TimeoutException异常
        throw new TimeoutException();
    //调用report方法解析状态值s,并且做出相应的响应
    return report(s);
}

3 FutureTask与线程池使用的注意

3.1 因丢弃任务而阻塞

线程池在使用FutureTask作为任务时(即submit系列方法),如果把拒绝策略设置为DiscardPolicy 或者DiscardOldestPolicy,即丢弃任务,并且在被拒绝的任务的Future对象上调用了get() 方法,那么调用线程将会由于任务永远不会执行而一直被阻塞。案例如下:

/**
 * @author lx
 */
public class ThreadPoolFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //自定义线程池,核心线程为0,最大线程为1,使用SynchronousQueue作为任务队列(没有容量),使用DiscardPolicy作为拒绝策略(丢弃任务)
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS,
                new SynchronousQueue<>(), new ThreadPoolExecutor.DiscardPolicy());
        //使用submit系列方法,传递一个Callable,内部会帮我们传递一个FutureTask对象给线程池
        //提交一个callable,返回1,获得一个FutureTask对象s1
        Future<Object> s1 = threadPoolExecutor.submit(() -> 1);
        //提交一个callable,返回2,获得一个FutureTask对象s2
        Future<Object> s2 = threadPoolExecutor.submit(() -> 2);
        threadPoolExecutor.shutdown();
        System.out.println(s1.get());
        System.out.println("s1返回成功");
        //由于第二个提交的任务被丢弃了,那么导致s2中的任务一直没能执行,而get方法是无限等待的,所以造成了一直阻塞
        System.out.println(s2.get());
        System.out.println("s2返回成功");
    }
}

3.2 未能抛出异常

使用submit系列方法时,执行任务过程中遇到的异常将不会直接抛出,而是被封装到了FutureTask中,只有调用get方法时才会抛出异常。因此,如果不需要会的返回值,那么直接使用execute方法提交任务更合适!

/**
 * @author lx
 */
public class ThrTask implements Runnable {
    private int a, b;

    private ThrTask(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        double re = a / b;
        System.out.println(re);
    }

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 1, TimeUnit.SECONDS, new SynchronousQueue<>());
        for (int i = 0; i < 5; i++) {
            //将不会抛出异常
            //threadPoolExecutor.submit(new ThrTask(5, i));
            //将会抛出异常
            threadPoolExecutor.execute(new ThrTask(5, i));
        }
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值