【Java多线程】线程池(三) Future、FutureTask

补.常用多线程并发获取返回结果方法汇总

描述 Future FutureTask CompletionService CompletableFuture
原理 Future接口 接口RunnableFuture的唯一实现类,RunnableFuture接口继承自Future+Runnable 内部通过阻塞队列+FutureTask接口 JDK8实现了Future, CompletionStage两个接口
多任务并发执行 支持 支持 支持 支持
获取任务结果的顺序 按照提交顺序获取结果 未知 支持任务完成的先后顺序 支持任务完成的先后顺序
异常捕捉 自己捕捉 自己捕捉 自己捕捉 原生API支持,返回每个任务的异常
建议 CPU高速轮询,耗资源,或者阻塞,可以使用,但不推荐 功能不对口,并发任务这一块多套一层,不推荐使用 推荐使用,没有JDK8CompletableFuture之前最好的方案 API极端丰富,配合流式编程,推荐使用

一.概述

  • 无论是的继承Thread,还是实现Runnable接口来创建线程。都有一个问题就是:任务完成后无法获取执行结果。如果需要获结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦
    • Java 1.5提供了Callable和Future,通过它们可以在`任务执行完后得到获取结果。

二.Runnable接口

接口里面只声明了一个run()方法,由于方法返回值为void类型,所以在执行完任务之后无法返回结果

public interface Runnable {
    
    
    public abstract void run();
}

三.Callable接口

位于java.util.concurrent包下,里面只声明了一个call()方法:

public interface Callable<V> {
    
       
    V call() throws Exception;
}

Callable是一个泛型接口(V代表异步任务的返回结果的类型),Callable接口可以看作是Runnable接口的补充,和Runnable相比,该方法有返回值并允许抛出异常

也就是说Future提供了2种功能:

  1. 可以返回任务执行完成结果
  2. 可以抛出异常

五.Future接口

位于java.util.concurrent包下,是一个泛型接口(V代表获取异步任务的返回结果的类型)。用于来获取一个可能还没有完成的异步任务的结果结合Callback接口可以在任务执行成功或失败后获取call()方法的返回结果

public interface Future<V> {
    
    
	//用来取消任务,如果取消任务成功,则返回true,如果取消任务失败,则返回false
	//参数mayInterruptIfRunning表示: 是否允许取消正在执行却没有执行完毕的任务
	/*
	如果设置true,则表示可以取消正在执行过程中的任务。
	1. 如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,
	2. 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true, 若mayInterruptIfRunning设置为false,则返回false
	3. 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。	
    */
    boolean cancel(boolean mayInterruptIfRunning);
    //表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
    boolean isCancelled();
    //用于表示任务是否完成,完成返回true
    boolean isDone();
    //用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回,可能会抛出异常。
    V get() throws InterruptedException, ExecutionException;
    //在指定的时间内等待task完成,并获取执行结果。可能抛出异常(含TimeoutException)
    V get(long timeout, TimeUnit unit)  throws InterruptedException, ExecutionException, TimeoutException;
}

也就是说Future提供了三种功能:

  1. 判断任务是否完成
  2. 能够中断任务
  3. 能够获取任务执行结果

使用Callable+Future并发执行任务并获取结果:

  • 使用线程池以submit()方式提交Callable接口任务,返回Future接口,添加进list,最后遍历FutureList且内部使用while轮询,并发获取结果
public class CallableFutureDemo {
    
    

    public static void main(String[] args) {
    
    
        Long start = System.currentTimeMillis();
        //开启多线程
        ExecutorService exs = Executors.newFixedThreadPool(10);
        try {
    
    
            //结果集
            List<Integer> list = new ArrayList<Integer>();
            //任务集
            List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();

            //1.高速提交10个任务,每个任务返回一个Future入list
            for (int i = 0; i < 10; i++) {
    
    
                futureList.add(exs.submit(new CallableTask(i + 1)));
            }

            Long getResultStart = System.currentTimeMillis();
            System.out.println("结果归集开始时间=" + new Date());

            //2.结果归集,用迭代器遍历futureList,高速轮询(模拟实现了并发),任务完成就移除
            while (futureList.size() > 0) {
    
    
                Iterator<Future<Integer>> iterable = futureList.iterator();
                //遍历一遍
                while (iterable.hasNext()) {
    
    
                    Future<Integer> future = iterable.next();
                    //如果任务完成取结果,否则判断下一个任务是否完成
                    if (future.isDone() && !future.isCancelled()) {
    
    
                        //获取结果
                        Integer i = future.get();
                        System.out.println("任务i=" + i + "获取完成,移出任务队列!" + new Date());
                        list.add(i);
                        //任务完成移除任务
                        iterable.remove();
                    } else {
    
    
                        Thread.sleep(1);//避免CPU高速运转,这里休息1毫秒,CPU纳秒级别
                    }
                }
            }

            System.out.println("list=" + list);
            System.out.println("总耗时=" + (System.currentTimeMillis() - start) + ",取结果归集耗时=" + (System.currentTimeMillis() - getResultStart));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            exs.shutdown();
        }
    }

    static class CallableTask implements Callable<Integer> {
    
    
        Integer i;

        public CallableTask(Integer i) {
    
    
            super();
            this.i = i;
        }

        @Override
        public Integer call() throws Exception {
    
    
            if (i == 1) {
    
    
                Thread.sleep(3000);//任务1耗时3秒
            } else if (i == 5) {
    
    
                Thread.sleep(5000);//任务5耗时5秒
            } else {
    
    
                Thread.sleep(1000);//其它任务耗时1秒
            }
            System.out.println("task线程:" + Thread.currentThread().getName() + "任务i=" + i + ",完成!" + new Date());
            return i;
        }
    }
}

结论:

  • 当我们使用线程池提交一个Callable任务后,会返回一个Future对象,然后在主线程某个时刻调用Future的get()方法,就可以获得异步执行的结果。
  • 在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。

开启定长为10的线程池,任务1耗时3秒,任务5耗时5秒,其他1秒。控制台打印如下:

结果归集开始时间=Wed Mar 03 18:52:19 CST 2021 //起始52秒
task线程:pool-1-thread-3任务i=3,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-9任务i=9,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-7任务i=7,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-6任务i=6,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-8任务i=8,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-10任务i=10,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-2任务i=2,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-4任务i=4,完成!Wed Mar 03 18:52:20 CST 2021
任务i=6获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021 // 一般任务耗时1秒,19+1=20,验证通过!
任务i=7获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=8获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=9获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=10获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=2获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=3获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=4获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-1任务i=1,完成!Wed Mar 03 18:52:22 CST 2021
任务i=1获取完成,移出任务队列!Wed Mar 03 18:52:22 CST 2021	//任务1 耗时3秒 19+3=22,验证通过!
task线程:pool-1-thread-5任务i=5,完成!Wed Mar 03 18:52:24 CST 2021
任务i=5获取完成,移出任务队列!Wed Mar 03 18:52:24 CST 2021	//任务5 耗时5秒 19+5=24,验证通过!
list=[6, 7, 8, 9, 10, 2, 3, 4, 1, 5] //多执行几遍,最后2个总是1,5最后加进去的,可实现按照任务完成先后顺序获取结果!
总耗时=5014,取结果归集耗时=5003 //符合逻辑,10个任务,定长10线程池,其中一个任务耗时3秒,一个任务耗时5秒,由于并发高速轮询,耗时取最长5秒

建议: 任务并行且按照完成顺序获取结果。使用很普遍,老少皆宜,就是CPU有消耗,可以使用!

六.FutureTask类

1.概述

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
在这里插入图片描述

FutureTask<T> (T代表异步任务的返回结果类型)类实现了RunnableFuture接口(唯一实现),该接口继承自Runnable和Future接口,弥补了`Future必须用线程池提交返回Future的缺陷,实现功能如下:

  1. 实现了Runnable接口,可开启单个线程执行。
  2. 实现了Future<v>接口,可接受Callable接口的返回值,futureTask.get()阻塞获取结果。

因此可以提交给Executor线程池执行,也可以通过Thread包装来直接执行

2.怎么使用

2.1. Thread方式

public static void main(String[] args) throws Exception {
    
    
        //直接通过匿名类方式创建带返回值的任务
        Callable<Integer> call = new Callable<Integer>() {
    
    
            @Override
            public Integer call() throws Exception {
    
    
                System.out.println("子线程在进行计算");
                int sum = 0;
                for (int i = 0; i < 100; i++) {
    
    
                    //每次累加休眠10毫秒 10 * 100 = 1000ms
                    Thread.sleep(10);
                    sum += i;
                }
                return sum;
            }
        };

        //创建获取返回值的任务
        FutureTask<Integer> futureTask = new FutureTask<>(call);

        //通过Thread启动任务
        Thread thread = new Thread(futureTask);
        //启动任务
        thread.start();

        //轮询判断任务是否完成,未完成则休眠没一次休眠500ms
        while (!futureTask.isDone()) {
    
    
            System.out.println("当前任务未完成,休眠500ms");
            Thread.sleep(500);
        }

        System.out.println("最终FutureTask返回结果:" + futureTask.get());
    }

在这里插入图片描述

2.2. 线程池方式

    public static void main(String[] args) {
    
    
        //直接通过匿名类方式创建带返回值的任务
        Callable<String> callable1 = new Callable<String>() {
    
    
            @Override
            public String call() throws Exception {
    
    
                Thread.sleep(2000);
                return Thread.currentThread().getName();
            }
        };
        //直接通过匿名类方式创建带返回值的任务
        Callable<String> callable2 = new Callable<String>() {
    
    
            @Override
            public String call() throws Exception {
    
    
                Thread.sleep(3000);
                return Thread.currentThread().getName();
            }
        };

        // 将Callable写的任务封装到一个由执行者调度的FutureTask对象
        FutureTask<String> futureTask1 = new FutureTask<>(callable1);
        FutureTask<String> futureTask2 = new FutureTask<>(callable2);

        // 创建线程池并返回ExecutorService实例
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.execute(futureTask1); //执行任务1
        executor.execute(futureTask2);//执行任务2

        //同时开启了两个任务
        long startTime = System.currentTimeMillis();
        while (true) {
    
    
            try {
    
    
                //  两个任务都完成,关闭线程池和服务
                if (futureTask1.isDone() && futureTask2.isDone()) {
    
    
                    System.out.println("FutureTask1 and FutureTask1 执行完成");
                    executor.shutdown();
                    return;
                }

                // 任务1完成则打印结果
                if (!futureTask1.isDone()) {
    
    
                    System.out.println("FutureTask1 执行完成=" + futureTask1.get());
                }

                System.out.println("等待 FutureTask2 执行完成");
                //200毫秒获取不到FutureTask2结果,则抛出异常
                String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
                if (s != null) {
    
    
                    System.out.println("FutureTask2 执行完成=" + s);
                }
            } catch (InterruptedException | ExecutionException e) {
    
    
                e.printStackTrace();
            } catch (TimeoutException e) {
    
    
               e.printStackTrace();
            }
            System.out.println("耗时:"+(System.currentTimeMillis() - startTime)+"ms");
        }
    }

在这里插入图片描述

2.3. 使用Callable+FutureTask并发执行任务并获取结果

和上面的 使用Callable+FutureTask并发执行任务并获取结果差不多,就是将Future替换成了FutureTask,同时每次替换的时候保存当前提交的 FutureTask

public class FutureTaskDemo {
    
    

    public static void main(String[] args) {
    
    
        Long start = System.currentTimeMillis();
        //开启多线程
        ExecutorService executor = Executors.newFixedThreadPool(10);
        try {
    
    
            //结果集
            List<Integer> list = new ArrayList<Integer>();
            //任务集
            List<FutureTask<Integer>> futureList = new ArrayList<FutureTask<Integer>>();

            //启动线程池,10个任务固定线程数为5
            for (int i = 0; i < 10; i++) {
    
    
                FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask(i + 1));
               //提交任务,添加返回,Runnable特性
                executor.submit(futureTask);
                //Future特性
                futureList.add(futureTask);
            }

            Long getResultStart = System.currentTimeMillis();
            System.out.println("结果归集开始时间=" + new Date());

            //2.结果归集,用迭代器遍历futureList,高速轮询(模拟实现了并发),任务完成就移除
            while (futureList.size() > 0) {
    
    
                Iterator<FutureTask<Integer>> iterable = futureList.iterator();
                //遍历一遍
                while (iterable.hasNext()) {
    
    
                    FutureTask<Integer> future = iterable.next();
                    //如果任务完成取结果,否则判断下一个任务是否完成
                    if (future.isDone() && !future.isCancelled()) {
    
    
                        //获取结果
                        Integer i = future.get();
                        System.out.println("任务i=" + i + "获取完成,移出任务队列!" + new Date());
                        list.add(i);
                        //任务完成移除任务
                        iterable.remove();
                    } else {
    
    
                        Thread.sleep(1);//避免CPU高速运转,这里休息1毫秒,CPU纳秒级别
                    }
                }
            }

            System.out.println("list=" + list);
            System.out.println("总耗时=" + (System.currentTimeMillis() - start) + ",取结果归集耗时=" + (System.currentTimeMillis() - getResultStart));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            executor.shutdown();
        }
    }

    static class CallableTask implements Callable<Integer> {
    
    
        Integer i;

        public CallableTask(Integer i) {
    
    
            super();
            this.i = i;
        }

        @Override
        public Integer call() throws Exception {
    
    
            if (i == 1) {
    
    
                Thread.sleep(3000);//任务1耗时3秒
            } else if (i == 5) {
    
    
                Thread.sleep(5000);//任务5耗时5秒
            } else {
    
    
                Thread.sleep(1000);//其它任务耗时1秒
            }
            System.out.println("task线程:" + Thread.currentThread().getName() + "任务i=" + i + ",完成!" + new Date());
            return i;
        }
    }
}

开启定长为10的线程池,任务1耗时3秒,任务5耗时5秒,其他1秒。控制台打印如下:

结果归集开始时间=Wed Mar 03 18:52:19 CST 2021 //起始52秒
task线程:pool-1-thread-3任务i=3,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-9任务i=9,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-7任务i=7,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-6任务i=6,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-8任务i=8,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-10任务i=10,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-2任务i=2,完成!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-4任务i=4,完成!Wed Mar 03 18:52:20 CST 2021
任务i=6获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021 // 一般任务耗时1秒,19+1=20,验证通过!
任务i=7获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=8获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=9获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=10获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=2获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=3获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
任务i=4获取完成,移出任务队列!Wed Mar 03 18:52:20 CST 2021
task线程:pool-1-thread-1任务i=1,完成!Wed Mar 03 18:52:22 CST 2021
任务i=1获取完成,移出任务队列!Wed Mar 03 18:52:22 CST 2021	//任务1 耗时3秒 19+3=22,验证通过!
task线程:pool-1-thread-5任务i=5,完成!Wed Mar 03 18:52:24 CST 2021
任务i=5获取完成,移出任务队列!Wed Mar 03 18:52:24 CST 2021	//任务5 耗时5秒 19+5=24,验证通过!
list=[6, 7, 8, 9, 10, 2, 3, 4, 1, 5] //多执行几遍,最后2个总是1,5最后加进去的,可实现按照任务完成先后顺序获取结果!
总耗时=5014,取结果归集耗时=5003 //符合逻辑,10个任务,定长10线程池,其中一个任务耗时3秒,一个任务耗时5秒,由于并发高速轮询,耗时取最长5秒

建议Callbale+FutureTask多线程并发执行并结果归集,这里多套一层FutureTask比较鸡肋还是直接返回Future简单明了不建议使用

3.FutureTask类的实现

3.1.属性和构造方法

 	/*
     * 可能的状态变更:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
     //就是当前task的状态
    private volatile int state;
    private static final int NEW          = 0; //新建的
    private static final int COMPLETING   = 1; //完成的中间状态
    private static final int NORMAL       = 2; //已正常完成
    private static final int EXCEPTIONAL  = 3; //异常终止
    private static final int CANCELLED    = 4; //已取消
    private static final int INTERRUPTING = 5; //中断时的中间状态
    private static final int INTERRUPTED  = 6; //已中断
    
    //底层的Callable对象,它的call方法执行后会产生执行结果,执行完成后该属性会置为null*/
    private Callable<V> callable;
   //存储get()方法返回的执行结果或是抛出的异常 
    private Object outcome;
    // 执行该Callable任务的线程 
    private volatile Thread runner;
 	//CAS进行更新的waiting threads,WaitNode节点形成了一个链表,表示正在等待获取执行结果的线程 
    private volatile WaitNode waiters;
    
    //等待节点:存储等待执行获取任务执行结果的线程们
		static final class WaitNode {
    
    
	//表示等待结果的线程
    	volatile Thread thread;
    //表示下一个等待节点(线程)
    //表示等待任务执行结果的等待栈(数据结构是单向链表) 
	//   WaitNode是一个简单的静态内部类,一个成员变量thread表示等待结果的线程,另一个成员变量next表示下一个等待节点(线程)。
    volatile WaitNode next;
    	WaitNode() {
    
     thread = Thread.currentThread(); }
	}
    
    // 可直接操作内存的类。详情百度
    private static final sun.misc.Unsafe UNSAFE;
    //UNSAFE使用的,state成员变量在FutureTask类中的偏移量
    private static final long stateOffset;
    //UNSAFE使用的,runner成员变量在FutureTask类中的偏移量
    private static final long runnerOffset;
    //UNSAFE使用的,waiters成员变量在FutureTask类中的偏移量
    private static final long waitersOffset;
    static {
    
    
        try {
    
    
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = FutureTask.class;
            stateOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("state"));
            runnerOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("runner"));
            waitersOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("waiters"));
        } catch (Exception e) {
    
    
            throw new Error(e);
        }
    }

     //构造函数,给定一个Callable任务
    public FutureTask(Callable<V> callable) {
    
    
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       //  初始状态
    }
 
 	//创建一个执行Runnable的Future,返回结果在参数中的result里,如果不需要结果,传入null就行了
    public FutureTask(Runnable runnable, V result) {
    
    
        //将Runnable和result包装为Callable对象
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;     //  初始状态
    }

FutureTask的构造方法会初始化callable和state ,它有2个构造方法, 分别接受Callable和Runnable类型的待执行任务。但对于Runnable类型参数,它会调用Executors.callable将Runnable转换为Callable类型实例,以便于统一处理。

  • Executors.callable方法也很简单,它就返回了一个Callable的实现类RunnableAdapter类型的对象
public static <T> Callable<T> callable(Runnable task, T result) {
    
    
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}
static final class RunnableAdapter<T> implements Callable<T> {
    
    
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
    
    
        this.task = task;
        this.result = result;
    }
    public T call() {
    
    
        task.run();
        return result;
    }
}

3.2.运行状态

如上面的源码所示,FutureTask使用一个volatile int state的变量作为同步状态

state所有可能的取值有7个,分别如下:

  • NEW = 0; 初始状态,FutureTask刚被创建,正在执行中都是该状态。
  • COMPLETING = 1; 中间状态,表示执行完成正在对结果进行赋值,或正在处理异常
  • NORMAL = 2; 终止状态,表示执行完成,结果已经被赋值
  • EXCEPTIONAL = 3; 异常终止状态,表示执行过程已经被异常打断
  • CANCELLED = 4; 取消终止状态,表示执行过程已经被cancel操作终止
  • INTERRUPTING = 5; 中间状态,表示执行过程已开始并且被中断,正在修改状态
  • INTERRUPTED = 6; 终止状态,表示执行过程已开始并且被中断,目前已完全停止

初始状态是New状态,之后运行状态的变化只会发生在set()、setException()、cancel()方法时,可能出现的状态转换有如下情形:

  • NEW --> COMPLETING --> NORMAL :表示任务正常执行完成
  • NEW --> COMPLETING --> EXCEPTIONAL:表示任务执行过程种出现异常
  • NEW --> CANCELLED :表示任务被取消执行
  • NEW --> INTERRUPTING --> INTERRUPTED :表示任务的执行过程被中断

3.3.关键源码

3.3.1.两个状态检查方法

该类的两个状态检查方法如下:

	// 检查是否已经取消,当状态处于Cancelled、Interrupting、Interrupted时表示已经取消 
    public boolean isCancelled() {
    
    
        return state >= CANCELLED;
    }
    // 检查任务是否执行完成,只要不是New状态就代表已经执行了 
    public boolean isDone() {
    
    
        return state != NEW;
    }
3.3.2.run()方法的实现

run()方法的实现如下:

  • run()方法用于开始执行FututeTask,执行线程为当前线程,该方法会将任务从New状态转换为其他状态
    • run方法中可以看出,只要不是New状态就代表已经执行完了,因为状态设置是在call()方法执行完后进行的。
 public void run() {
    
    
        //如果不是处于New状态或者设置执行线程为当前线程失败,就直接返回
        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 {
    
    
                    //1.调用callable.call(),执行task,获取返回值
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
    
    
                   //出现异常就将结果置为空
                    result = null;
                    ran = false;
                    //2.FutureTask的异常处理关键: 设置出现的异常
                    setException(ex);
                }
                if (ran)
                    //3.成功执行后设置执行结果
                    set(result);
            }
        } finally {
    
    
            // 当前封装的线程设为null,
            runner = null;
            // state must be re-read after nulling runner to prevent leaked interrupts
            int s = state;
            //任务处于中断中的状态,则进行中断操作
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
	//异常设置
    protected void setException(Throwable t) {
    
    
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    
    //将状态位设置成中间状态COMPLETING
            //将执行结果设置为异常,完成任务,设置状态为Exceptional
            outcome = t;//设置输出为正常返回结果
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // 将状态更为最终状态NORMAL
            finishCompletion();//同下面讲解
        }
    }
    
    //正常返回值设置
    protected void set(V v) {
    
    
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    
    //将状态位设置成中间状态COMPLETING
            //设置执行结果,将状态设为成功执行的Normal
            outcome = v;//设置输出为正常返回结果
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 将状态更为最终状态NORMAL
            finishCompletion();//同下面讲解
        }
    }

  //由中间状态到最终状态变更时,都需进行的操作,会在set()、setException()、cancel()被调用
  //在任务被取消、正常完成或执行异常时会调用finishCompletion()方法,从而唤醒等待队列中的线程。
    private void finishCompletion() {
    
    
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
    
    
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
    
     //尝试将waiters全部置为null
                for (;;) {
    
    //将waiters下对应的链式thread挨个唤醒
                    Thread t = q.thread;
                    if (t != null) {
    
    
                        q.thread = null;
                        LockSupport.unpark(t);//唤醒操作,LockSupport.park()阻塞
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
        
        done();

        callable = null;        // 减少内存占用
    }
    
	 //子类可重写该方法,实现回调
	 protected void done() {
    
     }

    // 确保cancel(true)产生的中断发生在run或runAndReset方法过程中。 
    private void handlePossibleCancellationInterrupt(int s) {
    
    
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                //将当前线程从执行状态退出,等待中断
                Thread.yield(); 
    }
3.3.3.cancel()方法的实现

cancel()方法的实现:

  • 取消task,根据boolean值来决定是否可进行中断操作
 //cancel用于取消任务
    public boolean cancel(boolean mayInterruptIfRunning) {
    
    
         //如果状态不为NEW,且无法将状态更新为INTERRUPTING或CANCELLED,则直接返回取消失败
        if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        //状态转换成功后
        try {
    
     
            if (mayInterruptIfRunning) {
    
    
                //如果允许中断正在执行的任务,那就中断当前线程
                try {
    
    
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();//并不是实时取消!
                } finally {
    
     // final state
                    //设置最终状态为Interrupted
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
    
    
            //执行清理工作,包括移除正在等待的线程,激活done()方法,将callable任务置为null
            finishCompletion(); //同面讲解
        }
        return true;
    }
    
   
3.3.4.get()和get(long timeout, TimeUnit unit)方法

get()和get(long timeout, TimeUnit unit)方法实现:

  • 注意调用get方法的线程是另外一个线程,可能会有好多其他线程在等待FutureTask的执行结果,那么就把他们封装成一个个WaitNode(结构上面有讲解)去等待返回结果
 public V get() throws InterruptedException, ExecutionException {
    
    
        int s = state;
        //处于New或Completing状态
        if (s <= COMPLETING)
            //阻塞等待执行完成
            s = awaitDone(false, 0L);
        //根据执行状态返回执行结果
        return report(s);
    }

    public V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {
    
    
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        //如果是中间状态,就等待task完成,如果超过了指定时间,task仍未完成,则抛出超时异常
        if (s <= COMPLETING &&(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        //没超时就报告结果
        return report(s);
    }
    //报告执行结果,返回的可以是异常,或是正常结果
    private V report(int s) throws ExecutionException {
    
    
        Object x = outcome;
        if (s == NORMAL)
            //如果正常执行就返回结果
            return (V)x;
        if (s >= CANCELLED)
            //如果任务被取消或中断,抛出异常
            throw new CancellationException();
        //任务本身执行异常,则抛出该异常
        throw new ExecutionException((Throwable)x);
    }

	 //线程status为NEW和COMPLETING的时候,会进入awaitDone方法,表示要等待完成。
     //等待完成,可能是是中断、异常、正常完成,timed:true,考虑等待时长,false:不考虑等待时长
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    
    
        final long deadline = timed ? System.nanoTime() + nanos : 0L; //如果设置了超时时间
        WaitNode q = null;
        boolean queued = false;
        //死循环不断尝试获取结果
        for (;;) {
    
    
         /**
         *  有优先级顺序
         *  1、如果线程已中断,则直接将当前节点q从waiters中移出
         *  2、如果state已经是最终状态了,则直接返回state
         *  3、如果state是中间状态(COMPLETING),意味很快将变更过成最终状态,让出cpu时间片即可
         *  4、如果发现尚未有节点,则创建节点
         *  5、如果当前节点尚未入队,则将当前节点放到waiters中的首节点,并替换旧的waiters
         *  6、线程被阻塞指定时间后再唤醒
         *  7、线程一直被阻塞直到被其他线程唤醒
         *
         */
            //1.如果当前get()方法的线程被中断了,那么就从等待队列中移出,抛出异常
            if (Thread.interrupted()) {
    
    
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            //2.如果处于已完成态,直接返回完成状态就好了
            if (s > COMPLETING) {
    
    
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // 3.处于未完成态,就让出当前线程,重新竞争CPU
                Thread.yield();
            else if (q == null) // 4.如果当前线程还没有加入到等待队列,就新建一个WaitNode
                q = new WaitNode();
            else if (!queued)  // 5.添加到等待队列的头部
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
    
    // 6.超时等待
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
    
    
                    //如果超出了等待时间,就移出队列
                    removeWaiter(q);
                    return state;
                }
                //阻塞当前线程一段时间
                LockSupport.parkNanos(this, nanos);
            }
            else 
                //7.阻塞当前线程,等待被唤醒
                LockSupport.park(this);
        }
    }
	
	//移除等待执行的节点
	 private void removeWaiter(WaitNode node) {
    
    
        if (node != null) {
    
    
            node.thread = null; //将需要去除的节点,thread赋值为null
            retry:
            for (;;) {
    
     
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
    
    
                    s = q.next;
                    if (q.thread != null)
                        pred = q;
                    else if (pred != null) {
    
     //q.thread==null,表示该节点是需要在队列中去除的节点,故直接将pred.next=s,重组队列
                        pred.next = s;
                        if (pred.thread == null)  //如果这个pred节点恰好是需要去除的节点,则进行循环,重组队列
                            continue retry;
                    }
                    else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)) 
                        continue retry;
                }
                break;
            }
        }
 

上面方法中awaitDoneget()方法的核心每一个等待获取结果的线程最终都会在该方法上阻塞。它不断循环执行核心方法,执行流程是:

  1. 先检查调用get()方法的那个线程是否已经中断,如果中断了,那就从等待队列删除该节点抛出异常
  2. 如果处于已完成状态,也就是Normal、Exceptional、Cancelled、Interrupting、Interrupted中的某一种,那么就直接返回执行状态就好了
  3. 如果处于Completing正在执行中,就让出当前线程占用的CPU时间,防止资源量浪费,等待任务执行完成
  4. 如果还未加入等待队列,就将自己加入到等待队列头部
  5. 如果有超时时间,就阻塞一段时间,等待任务执行完后唤醒该操作,重新回到循环头部
  6. 如果没有超时时间,直接阻塞,等待被唤醒.

相关文章

FutureTask源码完整解读

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/114305241