Java 异步编程之 FutureTask

更多并发相关内容,查看==>Java 线程&并发学习目录

目录
Java 异步编程之 FutureTask
1、自定义Demo 获取线程执行结果
2、Callable 接口
3、Future 接口
4、FutureTask 类
4.1、FutureTask 部分源码说明

1、自定义Demo 获取线程执行结果

在Java多线程编程中,创建线程执行一般有两种方法

  • 实现Runnable接口,创建Task然后提交给线程执行
  • 继承Thread类,创建新线程执行

但是这两种方法都有个同样的问题,无法获得线程执行的结果,run方法都是返回void

其实也可以有方法去解决,再包装一层run方法,使得获取到执行结果,再通过另一个方法去获取该结果,例如如下的样例代码

image

image

image

最后输出的内容


image

从结果上可以看出来,确实是利用了创建的新线程计算出了当前的时间戳,符合我们最初级的要求

原理也非常的简单,只是重新包装了一下Runnable接口,在其void run() 方法中计算出期望的结果,然后存储到data字段,最后在主线程的方法中调用resutl.getData方法获取线程的结果,虽然这个实现是非常低级的,但是也已经说明了一些问题,我们想要实现从线程中获取计算结果,必须侵入到void run() 方法中,然后进行改造存储计算结果,提供其他的方法供使用方取得计算结果,在Java5中添加的Callable 接口就是为了实现该功能,接下来

2、Callable 接口

image

Callable 接口只有一个方法,V call() 提供给外界使用,其实和上文中自定义的Result接口功能接近,正如该接口的文档描述所说,获取任务执行的结果,运行中可能会抛出异常,和Runnable接口功能不同

一般不会单独使用这个Callable接口,更多的是在异步的获取线程结果,或者回调结果等

3、Future 接口

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;
}

在Java5中新增的接口,主要用途同样是在线程操作中,例如某线程得长时间等待才能得出计算结果,但是又不必须立马就使用,就可以使用该接口,把线程的计算结果赋值给Future对象中,然后等到使用的时候通过get方法就可以轻松的获取计算结果,继续操作,避免长时间的等待,可以提高效率

4、FutureTask 类

上面的demo肯定有关注到主方法中存在一个睡眠1s的操作,其实是为了确保下面的resutl.getData方法一定可以获取到值。这主要是没有Future类的get方法的功能,当没计算出结果时,就会长时间等待轮询,直到结果出现,
这点也恰好说明了Future是异步处理,同步等待结果,切记勿随便在主线程这样操作,避免夯住主线程,造成服务不可用的情况。

FutureTask就是吸收了Future的特点,再组合Callable的能力,从而实现获取线程结果,并且可以利用线程中断随时终止线程的执行,如下FutureTask类图

image
public class FutureTask<V> implements RunnableFuture<V> {
  
    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;

    private Callable<V> callable;
    private Object outcome; 
    private volatile Thread runner;
    private volatile WaitNode waiters;

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

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

如上FutureTask部分代码,就是传入一个callback对象,或者已经包含结果的Runnable对象,因为FutureTask本身也是继承了Runnable接口,所以可以直接当做一个Task丢给线程、线程池去运行的,看看FutureTask具体的样例代码


image

FutureTask是支持传递callback对象以及runnable对象的,不过由于rannable是不支持返回结果,必须传入特定的结果然后返回。所以更多的都是传入callback接口实现的对象
最后使用new Thread(futureTask).start();就可以了

4.1、FutureTask 部分源码说明

  • 1、为什么传递Runnable对象也可以
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        // 创建了一个新的callable的对象,重新包装一层而已,其实是个RunnableAdapter对象而已
        this.state = NEW;       // ensure visibility of callable
    }
    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();
            // 传递的是runnable对象,真正去调用runnable的run方法
            return result;
            // 返回已经设置好的结果
        }
    }
  • 2、FutureTask 的run方法
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();
                // 就是在这里调用callable.call 方法
                // 获取计算结果
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
               // ran为true,则意味着存在有效的结果,把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);
    }
}

到这里对Java的FutureTask类就有了比较清晰的认识,再次强调一点的是,get方法会长时间轮询,尽量避免在主线程执行该类操作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,518评论 4 56
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,243评论 0 23
  • Android Handler机制系列文章整体内容如下: Android Handler机制1之ThreadAnd...
    隔壁老李头阅读 4,398评论 2 12
  • 在Java中,使用线程来异步执行任务。Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来...
    Steven1997阅读 826评论 0 0
  • 萱昨晚说爸爸炒的菜特别好吃,老公今早六点四十就起床给萱做饭。心满意足的吃完喜欢的饭菜后,萱主动拿起《弟子规》读了起...
    sunfeng0912阅读 195评论 0 4

友情链接更多精彩内容