1. 为什么要Future机制

对比常见的两种创建线程的方式,一种是直接继承Thread,另外一种就是实现Runnable接口

Runnable的缺陷
① 在执行完任务之后无法获取执行结果,从Java 1.5开始,就提供了CallableFuture,通过它们可以在任务执行完毕之后得到任务执行结果

Runnable接口中的run方法本身的返回值是void

ThreadPoolExecutor 获取线程索引_get方法

② 在run方法中无法抛出checked Exception,因为覆盖的run方法就没有声明异常,我们只能通过try catch在run方法内部捕获

ThreadPoolExecutor 获取线程索引_线程池_02

为什么Runnable要这样设计?

异常的抛出总是一层一层的往外抛,最后抛给main方法,对于run方法来说,如果它抛出的异常,那么是由run的使用者也就是Thread类或者线程池来接收异常,而这两个使用者也不是由我们编写的,所以即使抛出还是没法处理异常

2 Callable

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

Callable的代码也非常简单,不同的是它是一个泛型接口,call()函数返回的类型就是创建Callable传进来的V类型。
学习Callable对比着Runnable,这样就很快能理解它。CallableRunnable的功能大致相似,Callable功能强大一些,就是被线程执行后,可以返回值,并且能抛出异常

3 Future

3.1 基本介绍

Future模式的核心思想是,一个方法的计算可能会很耗时,所以我为耗时的计算开一个子线程来进行,再通过future来获取执行结果;也就是能够让主线程将原来需要同步等待的这段时间用来做其他的事情,异步获得执行结果,所以不用一直同步等待去获得执行结果

ThreadPoolExecutor 获取线程索引_线程池_03

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();
   
    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;//超时了要取消任务
}

CallableFuture的关系

  • 我们可以通过Future.get()方法来获取Callable接口返回的执行结果,还可以通过Future.isDone()来判断任务是否已经执行完成了,以及取消任务,限时获取任务结果等
  • call()未被执行完毕前,调用get()的线程会被阻塞,直到call()方法返回了结果,此时future.get()才会拿到结果,主线程切换为runnable状态
  • 所以Future是一个存储器,他存储了call()的执行结果,而这个任务的执行时间是无法提前确定的
V get():获取结果

get方法的行为取决于Callable任务的状态,有五种情况

  • 任务正常完成,get方法立刻返回结果
  • 任务尚未完成,get将阻塞
  • 任务执行抛出异常,get方法会抛出ExecutionException,因为get方法方法签名的异常就是ExecutionException,所以无论call方法抛出什么异常,get抛出的都是ExecutionException
  • 任务被取消,get方法抛出CancellationException
  • 任务超时,V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;,抛出TimeoutException
boolean cancel(boolean mayInterruptIfRunning):取消
  1. 如果这个任务还没开始,那么任务会被正常取消,方法返回true
  2. 如果任务已经完成,返回false
  3. 如果任务已经开始执行,则取消方法会根据我们传入的mayInterruptIfRunning参数做判断
  • 传入true表示会发送中断信号中断他
  • 传入false则不会发送中断信号,但是不发送中断信号这个方法还有什么用处呢? 用处就在于前两种对方法未执行或者执行完成的判断

cancel(true)
所以,cancel(true)适用于能处理interrupt的任务
cancel(false)
适用于避免启动尚未启动的任务

boolean isDone()

异常等情况也是执行结束了

3.2 使用线程池的submit方法返回Future对象

首先要给线程池提交任务,提交时线程池会返回给我们一个空的Future容器,当线程任务执行完毕,线程池就会把结果填入Future中,之后我们从Future中获取结果

ThreadPoolExecutor 获取线程索引_get方法_04

public class FutureDemo {
    public static void main(String[] args){
        ExecutorService service = Executors.newFixedThreadPool(10);
        Future<Integer> future = service.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return new Random().nextInt();
            }
        });
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }
}
3.3 批量接收结果
public class FutureDemo {
    public static void main(String[] args){
        ExecutorService service = Executors.newFixedThreadPool(10);
        ArrayList<Future> list = new ArrayList<>();
        for(int i = 0; i<10; i++){
            Future<Integer> future = service.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return new Random().nextInt();
                }
            });
            list.add(future);
        }
        for(int i = 0; i<10; i++){
            Future<Integer> future = list.get(i);
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        service.shutdown();
    }
}
3.4 执行时异常

在我们get的时候才会抛出异常

public class FutureDemo {
    public static void main(String[] args){
        ExecutorService service = Executors.newFixedThreadPool(10);
        Future<Integer> future = service.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                throw new Exception();
            }
        });
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }
}

4 FutureTask

可以使用FutureTask获取Futrue和任务的结果,FutureTask是一种包装器,可以把Callable转化为FutureRunnable,他即实现了Runnable接口,由实现了Future接口,所以他既可以作为Runnable被线程执行,又能作为Future得到Callble的返回值

ThreadPoolExecutor 获取线程索引_线程池_05

ThreadPoolExecutor 获取线程索引_get方法_06


FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承自RunnableFuture

使用

FutureTask task = new FutureTask(new Callable() {
	@Override
	public String call() throws Exception {
		return "hi";
    }
});
new Thread(task).start();
System.out.println(task.get());

可以看一下它run方法的源码

public void run() {
	if (state != NEW ||
        !RUNNER.compareAndSet(this, null, Thread.currentThread()))
		return;
	try {
		Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();//我们写的callable
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
     	       set(result);//!!!set了结果,既然能set肯定就能get
        }
    } finally {
 		// runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
        	handlePossibleCancellationInterrupt(s);
    }
}

5. Future的注意点

在上面我们对于Future批量获取线程结果的时候,如果前面的结果比后面的线程慢执行完,那么依旧要等待前面的线程执行完,会带来性功能的损耗,可以使用CompletableFuture改进