从源码入门Future与FutureTask的使用

Future 与 FutureTask

A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation. The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready.

重点: Future类是 异步计算 的结果类,isDone()方法来判断当前计算是否完成,get()取得异步计算结果值,cancel(boolean mayInterrupt)来中断该任务,这里的中断需要由传入参数来决定:

  • 如果当前任务还没有开始,那么调用cancel()会正常取消任务执行
  • 如果当前任务已经开始,调用cancel(false)会允许任务执行完毕,而calcel(true)则会强制当前任务结束

而我们知道,在Java实现异步,需要用到多线程,那么很自然的我们需要一个子类来实现Runnable接口与Future接口,通过另外开辟一个新线程的方式来计算结果
实现Future的子类FutureTask

从类关系图可以看出,FutureTask实现了RunnableFuture这个整合了Runnable接口与Future接口的类。通过给新线程传递FutureTask参数并使用start()方法,便可创建多线程环境:

        FutureTask<Integer> futureTask = new FutureTask<>(new MyCall());
        Thread thread = new Thread(futureTask);//先使用Thread来创建线程
        thread.start();
        int res = futureTask.get();
        System.out.println("结果 : " + res);
public class MyCall implements Callable<Integer> {
    public Integer call() throws Exception {
        int res = 0;
        for(int i = 0; i < 100; i++){
            res += i;
        }
        return res;
    }
}

注意到这里使用了FutureTask对Callable对象作封装,我们先来看一下FutureTask构造函数:

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

FutureTask需要一个Callable对象作为参数,并检查Callable对象是否为空,然后会将成员变量private Callable<V> callable 设置为传入对象,并初始化当前任务状态为NEW

使用Thread创建线程(补充,可直接快速划过)
随后使用Thread创建新线程,传入FutureTask对象

public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }

启动start()方法,由Java内置start0()创建新线程:

public synchronized void start() {
        if (threadStatus != 0)//threadStatus表明线程状态为NEW,跟上面FutureTask一样
            throw new IllegalThreadStateException();

        group.add(this);//通过SecurityManager取得system线程组,将新的Thread加到线程组中,如果构造函数传入了线程组,则使用此线程组

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

如果当前线程启动发生错误,由于当前线程被加入了当前的线程组(在不创建其他ThreadGroup时默认加到system线程组),所以调用当前线程组threadStartFailed(Thread t)方法:

void threadStartFailed(Thread t) {
        synchronized(this) {
            remove(t);
            nUnstartedThreads++;
        }
    }

而进一步调用了remove(Thread t)方法:

private void remove(Thread t) {
        synchronized (this) {
            if (destroyed) {
            //并发处理,如果当前线程已经被销毁,则直接返回
                return;
            }
            for (int i = 0 ; i < nthreads ; i++) {
            //循环ThreadGroup中threads[]线程数组,找到未能启动线程,然后覆盖此线程
                if (threads[i] == t) {
                    System.arraycopy(threads, i + 1, threads, i, --nthreads - i);
                    // Zap dangling reference to the dead thread so that
                    // the garbage collector will collect it.
                    threads[nthreads] = null;
                    break;
                }
            }
        }
    }

这里多说一句,上面的threads[nthreads] = null是 'Effective Java’推荐的做法,如果当前对象引用不再使用,应该手动置为null使其被GC以防内存泄露

说了这些,现在线程也启动起来了,这个线程会自动执行我们传入的Runnable对象的run()方法,当然了由于我们传入的是FutureTask对象,那又有什么不同呢?

FutureTask增强的run方法:

public void run() {
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            //当前任务不是新任务(比如COMPLETING,NORMAL,INTERRUPTED等,直接返回)
            return;
        try {
            Callable<V> c = callable;//使用了我们之前传入的Callable对象
            if (c != null && state == NEW) {//检查合法性
                V result;//我们需要的异步结果
                boolean ran;
                try {
                    result = c.call();//手动调用call()方法,并将返回值给result
                    ran = true;
                } catch (Throwable ex) {//如果发生非运行期错误,设置STATE = EXCEPTIONAL
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);//运行成功后,将类成员变量outcome设置为result
            }
        } finally {
            runner = null;//运行结束后,将当前运行线程置null
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

来看一下上面的set(V val)方法:

    protected void set(V v) {
    //通过内置run()方法中调用此方法,设置本次运行结果的最终状态为NORMAL
        if (STATE.compareAndSet(this, NEW, COMPLETING)) {
            outcome = v;
            STATE.setRelease(this, NORMAL); // final state
            finishCompletion();
        }
    }

好啦!现在我们的Runnable对象FutureTask task1已经执行完毕了,我们调用T get()来获取执行的结果:
注意到T get()方法有重载方法

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)//如果当前状态是待完成或者NEW
            s = awaitDone(false, 0L);//等待执行完毕
        return report(s);
    }

IMPORTANT :
注意,调用了awaitDone()后,会将其他线程挂起,执行完当前任务所在线程,举个例子,修改上述的MyCall类,使线程休眠1000毫秒,在主线程中调用封装了MyCall对象的FutureTask时(使用新Thread),如果执行其他操作,1s以后尝试获取结果,那么会顺利的获取。如果调用MyCall对象后立即使用int res = futureTask.get(),会造成主线程挂起,等待返回结果后再执行

如果get()方法传入了long参数和TimeUnit参数:

    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)
            //如果在timeout时间之类没有执行完毕,awaitDone返回<=1的数,get抛出超时错误
            throw new TimeoutException();
        return report(s);
    }

取得异步结果的最后一步便是调用report(int state)方法:

private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)//结果正常情况下,转为泛型类型直接返回outcome
            return (V)x;
        if (s >= CANCELLED)//CANCELLED,INTERRUPTING,INTERRUPTTED级别错误
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);//EXCEPTIONAL级别错误
    }

综上,FutureTask对于Callable对象的封装可表示如下:
流程描述

当然了这篇文章主要是来介绍FutureTask对于Callable的封装使用,Runnable对象可通过Executor类中RunnableAdapter<T>适配器转换为Callable对象:

    private static final class RunnableAdapter<T> implements Callable<T> {
        private final Runnable task;
        private final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;//注意这里返回了传入的值,所以使用get方法拿到的是传入的值
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值