并发系列--Future和Callable

一、Runnable的缺陷


在多线程开发中,一般都会使用Runnable来处理异步任务,但是它有一定的缺陷
1、不能返回一个返回值
2、也不能抛出checked Exception

接着就可以引入下面主题,CallableFuture,它在Android源码中很多地方使用到了,比如AsyncTask中,学会了CallableFuture,就可以更方便看源码。

二、有返回值Callable接口


它是一个接口,约定了线程要做的事情,作用:

1、类似于Runnable,被其它线程执行的任务
2、实现call方法
3、有返回值
4、可以抛出异常

Callable一般和Future一起使用,不会单独使用

三、Future类


Future的作用


方法的计算可能很耗时,计算的过程中,没有必要在原地等返回,子线程执行过程中可以取做其它事情,执行完成,Future可以获取计算的结果。

Callable和Future的关系


我们可以用Future.get来获取Callable接口返回的执行结果,还可以通过Future.isDone()来判断任务是否已经执行完了,以及取消这个任务,限时获取任务的结果等

在call()方法未执行完毕之前,调用get()的线程(假定此时是主线程)会被阻塞,直到call()方法返回了结果后,此时future.get()才会得到该结果,然后主线程才会切换到runnable状态

所以Future是一个存储器,它存储了call()这个任务的结果,而这个任务的执行时间是无法提前确定的,因为这完全取决于call()方法执行的情况

四、Future的主要方法:一共5个


get方法


get方法的行为取决于Callable任务的状态,只有以下5种情况

1、任务正常完成:get方法会立刻返回
2、任务尚未完成(任务还没开始或进行中):get将会阻塞并直到任务完成
3、任务执行过程中Execption:get方法会抛出ExecutionException:这里抛出异常,是call()执行时产生的那个异常,看到这个异常类型时java.util,concurrent.ExecutionException。不论call()执行时抛出的异常类型是什么,最后get方法抛出的异常类型都是ExecutionException。
4、任务被取消:get方法会抛出CancellationException
5、任务超时:get方法有一个重载方法,是传入一个延迟时间的,如果时间到了还没有获得结果,get方法就会抛出TimeoutException

get(long timeout, TimeUnit unit):有超时的获取


超时的需求很常见

用get(long timeout, TimeUnit unit)方法时,如果call()在规定时间内完成任务,那么就会正常获取到返回值;而如果再指定时间内没有计算出结果,那么就会抛出TimeoutException

超时不获取,任务需取消

cancel()方法


取消任务的执行

isDone()方法


判断线程是否执行完毕

isCancelled()方法


判断是否被取消

五、Future用法


几种常用的用法

用法一 :线程池的submit方法返回Future对象


首先,我们要给线程池提交我们的任务,提交时线程池就会立刻返回给我们一个空的Future容器。当线程的任务一旦执行完毕,也就是当我们可以获取结果的时候,线程池便会把该结果填入到之前给我们的那个Future中去(而不是创建一个新的Future),我们此时便可以从该Future中获得任务执行的结果

//演示一个Future的使用方法
@Test
public void test02(){
    ExecutorService service = Executors.newFixedThreadPool(10);
    Future<Integer> future = service.submit(new CallableTask());
    try {
        System.out.println(future.get());
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    service.shutdown();
}
class CallableTask implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {

        Thread.sleep(3000);

        return new Random().nextInt();
    }
}

1、Callable定义我们需要做的事情,是可以有返回值的
2、传入Callable任务,可以获取Future返回值
3、通过Future.get可以获取子线程执行结果

用法二:多个任务,用Future数组来获取结果


//演示批量提交任务时,用List来批量获取结果
@Test
public void test03() throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(20);
    ArrayList<Future> futures = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        Future<Integer> future = service.submit(new CallableTask());
        futures.add(future);
    }
    Thread.sleep(5000);
    for (int i = 0; i < 20; i++) {
        Future<Integer> future = futures.get(i);
        try {
            Integer integer = future.get();
            System.out.println(integer);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class CallableTask implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {

        Thread.sleep(3000);

        return new Random().nextInt();
    }
}

用法三:任务执行过程中抛出Exception和isDone展示


//演示get方法过程中抛出异常,for循环为了演示抛出Exception的时机:并不是说一产生异常就抛出,直到我们get执行时,才会抛出。
@Test
public void test04(){
    ExecutorService service = Executors.newFixedThreadPool(20);
    Future<Integer> future = service.submit(new CallableTask04());
    try {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
            Thread.sleep(500);
        }
        System.out.println(future.isDone());
        future.get();
    } catch (ExecutionException e) {
        e.printStackTrace();
        System.out.println("ExecutionException异常");
    } catch (InterruptedException e) {
        e.printStackTrace();
        System.out.println("InterruptedException异常");
    }
}

class CallableTask04 implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        throw new IllegalArgumentException("Callable抛出异常");
    }
}

执行get才会抛出异常

用法四:默认广告的超时和取消


private final Ad DEFAULT_AD = new Ad("无网络时候的默认广告");
private final ExecutorService service = Executors.newFixedThreadPool(2);

class FetchAdTask implements Callable<Ad>{

    @Override
    public Ad call() throws Exception {
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            System.out.println("sleep期间被中断了");
            return new Ad("被中断时候的默认广告");
        }
        return new Ad("淘宝广告");
    }

}

public void printAd(){
    Future<Ad> f = service.submit(new FetchAdTask());
    Ad ad;

    try {
        ad = f.get(2000, TimeUnit.MILLISECONDS);
    } catch (ExecutionException e) {
        e.printStackTrace();
        ad = new Ad("异常时候的默认广告");
    } catch (InterruptedException e) {
        e.printStackTrace();
        ad = new Ad("中断时候的默认广告");
    } catch (TimeoutException e){
        //            e.printStackTrace();
        ad = new Ad("异常时候的默认广告");

        System.out.println("超时了,未获取到广告");

        //cancel true 中断 false不会中断
        boolean cancel = f.cancel(true);
        System.out.println("cancel的结果" + cancel);
    }

    service.shutdown();
    System.out.println(ad);
}

//演示get的超时方法,需要注意超时后处理,调用future.cancel()。演示cancel传入true和false的区别,代表是否中断正在执行的任务。
@Test
public void test05(){

    printAd();

}

六、cancel:取消任务的执行


1、如果这个任务还没有开始执行,那么这种情况最简单,任务会被正常的取消,未来也不会被执行,方法返回true

2、如果任务已完成,或者已取消,那么cancel()方法会执行失败,方法返回false

3、如果这个任务已经开始执行了,那个这个取消方法将不会直接取消该任务,而是会根据我们填的参数mayInterruptIfRunning做判断;

Future.cancel(true)适用于:

1、任务能够处理interrupt

Future.cancel(false)仅用于避免启动尚未启动的任务,适用于:

1、未能处理interrupt的任务

2、不清除任务是否支持取消

3、需要等待已经开始的任务执行完成

七、用FutureTask来获取结果


FutureTask是一种包装器,可以把Callable转化成Future和Runnable,它同时实现二者的接口

在这里插入图片描述

用Callable实例当作参数,生成FutureTask的对象,然后把这个对象当作一个Runnable对象,用线程池或另起线程区执行这个Runnable对象,最后通过FutureTask获取刚才执行的结果。

/**
 * 描述:     演示FutureTask的用法
 */
public class FutureTaskDemo {

    public static void main(String[] args) {
        Task task = new Task();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(task);
//        new Thread(integerFutureTask).start();
        ExecutorService service = Executors.newCachedThreadPool();
        service.submit(integerFutureTask);

        try {
            System.out.println("task运行结果:"+integerFutureTask.get());

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class Task implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("子线程正在计算");
        Thread.sleep(3000);
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

八、Future的使用注意点


1、当for循环获取future的结果时,容易发生一部分线程很慢的情况,get方法调用时应使用timeout限制

2、Future的生命周期不能后退
生命周期只能前进,不能后退。就和线程池的生命周期一样,一旦完全完成了任务,它就永远停在了“已完成”的状态,不能重来。

使用完之后,开始分析源码

九、Callable实现


接口定义:

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

返回值是一个泛型,可以定义成为任何类型,并且可以看到是可以抛出异常的,在向线程池提交任务时,可以提交RunnableCallable两种任务类型,

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(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

这两种任务都会转化为Callable

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

RunnableAdapterCallable一个实现,然后会包装到FutureTask中,线程运行的具体任务是通过它, FutureTask 实现了 RunnableFuture 接口

public class FutureTask<V> implements RunnableFuture<V> {

}

RunnableFuture 又实现了 Runnable, Future 两个接口。

十、Future实现


Callable 是可以返回子线程执行结果的,在获取结果的时候,就需要用到 Future接口

Future接口注释写了这些:

1、定义了异步计算的接口,提供了计算是否完成的 check、等待完成和取回等多种方法;
2、如果想得到结果可以使用 get 方法,此方法(无参方法)会一直阻塞到异步任务计算完成;
3、取消可以使用 cancel 方法,但一旦任务计算完成,就无法被取消了。

Future接口定义了这些方法:

public interface Future<V> {

// 如果任务已经成功了,或已经取消了,是无法再取消的,会直接返回取消成功(true)
// 如果任务还没有开始进行时,发起取消,是可以取消成功的
// 如果取消时,任务已经在运行了,mayInterruptIfRunning 为 true 的话,就可以打断运行中的线程
    boolean cancel(boolean mayInterruptIfRunning);

// 返回线程是否已经被取消了,true 表示已经被取消了
// 如果线程已经运行结束了,isCancelled 和 isDone 返回的都是 true
    boolean isCancelled();

// 线程是否已经运行结束了
    boolean isDone();

// 等待结果返回
// 如果任务被取消了,抛 CancellationException 异常
// 如果等待过程中被打断了,抛 InterruptedException 异常
    V get() throws InterruptedException, ExecutionException;

// 等待,但是带有超时时间的,如果超时时间外仍然没有响应,抛 TimeoutException 异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

从接口上可以看出,Future 定义了各种方法对任务进行了管理,比如说取消任务,得到任务的
计算结果等等。

十一、RunnableFuture


RunnableFuture 也是一个接口,定义如下:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture接口的最大目的,是让Future可以对Runnable进行处理,可以取消Runnable,查看Runnable是否完成等等。

十二、统一Callable和Runnable


前面提到了Runnable会转化为Callable,然后作为FutureTask的属性,FutureTask统一了 Callable 和 Runnable,提供了两者一系列的转化方法。再来看下FutureTask的类定义

FutureTask类定义


public class FutureTask<V> implements RunnableFuture<V> {
}

FutureTask 实现了 RunnableFuture 接口,也就是说间接实现了Runnnable 接口(RunnableFuture 实现了 Runnnable 接口),就是说 FutureTask 本身就是个 Runnnable,同时 FutureTask 也实现了 Future,也就是说 FutureTask 具备对任务进行管理的功能(Future 具备对任务进行管理的功能)。

FutureTask属性


 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
 private Callable<V> callable;
// 异步线程返回的结果
 private Object outcome; // non-volatile, protected by state reads/writes
// 当前任务所运行的线程
 private volatile Thread runner;
// 记录调用 get 方法时被等待的线程
 private volatile WaitNode waiters;

FutureTask构造器


Future两个构造器

//使用 Runnable 初始化,并传入 result 作为返回结果。
// Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了
public FutureTask(Runnable runnable, V result) {
    // Executors.callable 方法把runnable适配成 RunnableAdapter,RunnableAdapter 实现了Callable
    this.callable = Executors.callable(runnable, result);
    //任务状态初始化
    this.state = NEW;       // ensure visibility of callable
}
// 使用 Callable 进行初始化
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    //任务状态初始化
    this.state = NEW;       // ensure visibility of callable
}

Runnable的两个构造器,只有一个目的,就是通过Executors.callable把入参转化为RunnableAdapter,主要是因为Callable的功能比Runnable丰富,Callable有返回值,而Runnable没有。

这是一个典型的适配模型,我们要把 Runnable 适配成 Callable,首先要实现 Callable 的接口,接着在 Callable 的 call 方法里面调用被适配对象(Runnable)的方法。

FutureTask对Future 接口方法的实现


几个关键的方法实现

get

get有无限阻塞和带超时时间两种方法,一般建议使用带超时时间的方法,源码如下

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);
}
// 等待任务执行完成
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    // The code below is very delicate, to achieve these goals:
    // - call nanoTime exactly once for each call to park
    // - if nanos <= 0L, return promptly without allocation or nanoTime
    // - if nanos == Long.MIN_VALUE, don't underflow
    // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
    //   and we suffer a spurious wakeup, we will do no worse than
    //   to park-spin for a while
    long startTime = 0L;    // Special value 0L means not yet parked
    WaitNode q = null;
    boolean queued = false;
    //无限循环
    for (;;) {
        //当前任务状态
        int s = state;
        //当前任务已经执行完了,返回
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        //如果正在执行,当前线程让出 cpu,重新竞争,防止cpu飙高
        else if (s == COMPLETING)
            // We may have already promised (via isDone) that we are done
            // so never return empty-handed or throw InterruptedException
            Thread.yield();
        //如果线程已经被打断了,删除,抛异常    
        else if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        // 如果第一次运行,新建waitNode,当前线程就是waitNode 的属性
        else if (q == null) {
            if (timed && nanos <= 0L)
                return s;
            q = new WaitNode();
        }
        else if (!queued)
            queued = U.compareAndSwapObject(this, WAITERS,
                                            q.next = waiters, q);
        else if (timed) {
            final long parkNanos;
            if (startTime == 0L) { // first time
                startTime = System.nanoTime();
                if (startTime == 0L)
                    startTime = 1L;
                parkNanos = nanos;
            } else {
                long elapsed = System.nanoTime() - startTime;
                if (elapsed >= nanos) {
                    removeWaiter(q);
                    return state;
                }
                parkNanos = nanos - elapsed;
            }
            // nanoTime may be slow; recheck before parking
            // 没有过超时时间,线程进入TIMED_WAITING状态
            if (state < COMPLETING)
                LockSupport.parkNanos(this, parkNanos);
        }
        else
            LockSupport.park(this);
    }
}

get 方法虽然名字叫做 get,但却做了很多 wait 的事情,当发现任务还在进行中,没有完成时,就会阻塞当前进程,等待任务完成后再返回结果值。阻塞底层使用的是 LockSupport.park方法,使当前线程进入 WAITING 或TIMED_WAITING 状态。

run

/**
 * run方法可以直接被调用
 * 也可以开启新的线程调用
 */
public void run() {
// 状态不是任务创建,或者当前任务已经有线程在执行了,直接返回
    if (state != NEW ||
        !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        // 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)
            //将result赋值给outcome
                set(result);
        }
    } 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);
    }
}

1、run方法是没有返回值的,通过给outcome属性赋值(set(result)),get时就能从outcome属性中拿到返回值。
2、FutureTask 两种构造器,最终都转化成了 Callable,所以在 run 方法执行的时候,只需要执行 Callable 的 call 方法即可,在执行 c.call()代码时,如果入参是 Runnable 的话, 调用路径为 c.call() -> RunnableAdapter.call() -> Runnable.run(),如果入参是 Callable 的话,直接调用。

cancel

// 取消任务,如果正在运行,尝试去打断
public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW &&//任务状态不是创建,并且不能把new状态置为取消,直接返回false
          U.compareAndSwapInt(this, STATE, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
       // 进行取消操作,打断可能会抛出异常,选择 try finally 的结构
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
            //状态设置成已打断
                U.putOrderedInt(this, STATE, INTERRUPTED);
            }
        }
    } finally {
    // 清理线程
        finishCompletion();
    }
    return true;
}

十三、参考


1.面试官系统精讲Java源码及大厂真题

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值