Java并发学习(二十八)-Callable及FutureTask分析

过了个春节,感觉还是家里面舒服啊~~
最开始接触Java多线程时候,主要就是Thread和Runnable,即继承Thread或者实现Runnable接口,再通过start方法即可开一个线程。但是run方法是没有返回值的,就类似于泼出去的水,你是无法知道其内部执行状态的。当然可以通过一些特定的volatile变量来标识。
Java5之后,出现了一个新的接口Callable,它的功能就是Runable的功能,但是它能够返回一个执行结果。

下面看一个具体例子:

利用Callable来实现多线程

public class FutureTest {
    public static void main(String[] args) {
        CallableThreadDemo ctd = new CallableThreadDemo();         //一个Callable的线程
         // 以Callable方式,需要FutureTask实现类的支持,用于接收运算结果
        FutureTask<Integer> result = new FutureTask<Integer>(ctd); 
        new Thread(result).start();                       //放到Thread里面执行。
        try {
            Integer sum = result.get(); // 获取前面callable的执行结果。
            System.out.println(sum);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @author anla7856
 * 实现Callable接口,返回Integer,返回计算的1到50的和
 */
class CallableThreadDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 50; i++) 
            sum += i;
        return sum;
    }
}

如上代码,并没有用到Runnable来开启一个线程,而是通过Callable的方式。Callable和Future,一个产生结果,一个拿到结果。
Callable接口类似于Runnable,但是Runnable不会返回结果(上文已提),并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。

下面看看Callable接口:

Callable

Callable接口定义很简单,就一个方法call方法,它是具有返回值并且能够抛出异常的:

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

由上述定义可以分析:
- V代表执行的结果的泛型
- 能够抛出异常
- FunctionalInterface注释,一个函数式方法

下面对比的看看Runnable接口:

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

不同之处就不多叙述。

Future

Future是用来获取结果的,它代表一个异步计算的结果,可以理解为它是一个线程的管家,可以尝试取消线程,或者查看线程
执行的状态,以及结果。例如,当向线程池(下一篇文章讲述)提交一个Callable对象时,可以用一个Future管理这个Callable以及获取这个Callable对象的结果。

//此段代码摘自JDK1.8
 void showSearch(final String target) throws InterruptedException {
     Future<String> future = executor.submit(new Callable<String>() {
          public String call() {
              return searcher.search(target);
     }});
     displayOtherThings(); // do other things while searching
     try {
         displayText(future.get()); // use future
     }catch(ExecutionException ex) {
         cleanup();
         return; 
      }
  }

下面看看Future接口具体内容:

public interface Future<V> {

    /**
    * 试图取消执行此任务。
    * 如果任务已完成,已被取消或因其他原因无法取消,此尝试将失败。
    * 如果成功,并且此任务在cancel调用时尚未开始,则此任务不应运行。
    * 如果任务已经开始,那么mayInterruptIfRunning参数确定执行这个任务的线程是否应该被中断以试图停止任务。
    * 在此方法返回后,随后的调用isDone()将始终返回true。如果此方法返回,后续调用isCancelled() 将始终true返回true。
    */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
    *true如果此任务在正常完成之前取消,则返回。
    */
    boolean isCancelled();
    /**
    * 这几种情况都会返回true:normal termination, an exception, or cancellation
    */
    boolean isDone();
    //获取返回结果
    V get() throws InterruptedException, ExecutionException;
    //有超时的获取返回结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

如上,Future可以看作Callable提交执行的管家,它可以对Callable本身做一些操作。

下面看看FutureTask:

FutureTask

FutureTask是一个支持取消行为的异步任务执行器,如果在当前线程中需要执行比较耗时的操作,但又不想阻塞当前线程时,可以把这些作业交给FutureTask,另开一个线程在后台完成,当当前线程将来需要时,就可以通过FutureTask对象获得后台作业的计算结果或者执行状态。

JDK1.7及之前,FutureTask 通过使用内部类Sync继承AQS来实现,内部使用的AQS的共享锁。

JDK1.8没有使用AQS,而是自己实现了一个同步等待队列,在结果返回之前,所有的线程都被阻塞,存放到等待队列中。

FutureTask实现RunnableFuture接口,而RnnableFutrue则是同时继承了Runnable和Future接口(注意和多继承区分)。
咋一看会有点糊涂,因为Runnable接口是没有返回值的,那么它要Future接口干吗呢?

而在上文例子中,则是通过以下两行代码执行的:

    FutureTask<Integer> result = new FutureTask<Integer>(ctd); 
    new Thread(result).start();                       //放到Thread里面执行。

这样一看会不会有点感觉,最终还是会传递一个Callable对象给FutureTask的,接下来看看其实现。
先看里面字段:

    /**
     * 初始与new,
     * 然后可能会进入completing,此时outcome就会被设置。
     * 或者进入interrupting,此时如果调用cancel,就会成功,只有这个阶段调用cancel。
     */
    private volatile int state;                      //volatile状态
    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;                //当running后,就会变为null
    private Object outcome;   //执行结果,非volatile,被state读写保护,利用state加锁。
    private volatile Thread runner;            //运行线程。
    private volatile WaitNode waiters;             //waiters,等待者。当前线程的等待着。

在FutureTask中,对线程的控制有7种状态,可能的状态有以下几种状态:

  • NEW -> COMPLETING -> NORMAL
  • NEW -> COMPLETING -> EXCEPTIONAL
  • NEW -> CANCELLED
  • NEW -> INTERRUPTING -> INTERRUPTED

FutureTask可以既可以接受Runnable,也能接受Callable类型线程,有两种构造方法:

public FutureTask(Callable<V> callable) {          
 //接受传入的callable,让FutureTask来帮他执行。
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       //设置状态。
    }

以及:

 public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);           //利用Executors包装一个callable
        this.state = NEW;       // ensure visibility of callable
    }

接下来依次看它的方法:

run方法
   public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))        
            //如果已经被运行了。并且强行获取runner失败。那么直接返回,因为有人已经执行或者已经执行完了。
            return;
        try {
            Callable<V> c = callable;     //获取callable。
            if (c != null && state == NEW) {    //新的,自己开始执行。
                V result;
                boolean ran;
                try {
                    result = c.call();    //执行call,或者结果result
                    ran = true;       //表示已经执行玩了,用于最后set执行结果。
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);           //有错误,就set错误返回
                }
                if (ran)
                    set(result);
            }
        } finally {
            runner = null;          //runner置空防止calls再次运行,因为concurrent线程已经运行玩了
            int s = state;         //state一定要runner置空后重新读取。
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

看看所涉及的handlePossibleCancellationInterrupt方法:

 /**
     * Ensures that any interrupt from a possible cancel(true) is only
     * delivered to a task while in run or runAndReset.
     * 判断该任务是否正在响应中断,如果中断没有完成,则等待中断操作完成
     * 同时一定程度上,cancel的效果可以传递过来到run方法中。
     */
    private void handlePossibleCancellationInterrupt(int s) {
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); // wait out pending interrupt
    }

上述run方法只执行一次,并且会改变相应的标志为,
而runAndReset则既不设置结果也不设置state仅仅是运行一次:

    protected boolean runAndReset() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            //尝试获取线程。
            return false;
        boolean ran = false;
        int s = state;
        try {
            Callable<V> c = callable;
            if (c != null && s == NEW) {
                try {
                    c.call(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } finally {

            runner = null;

            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        return ran && s == NEW;
    }

如上述代码,runAndReset代码和run方法基本一致,但是它不改变state以及result的值。

cancel方法
    public boolean cancel(boolean mayInterruptIfRunning) {     //cancel方法。
        //如果state为new,并且尝试设置state值,但是cas设置失败,那么直接返回false。
        if (!(state == NEW &&                                    //cas去设置state。
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))  //设置为interrupting或者cancelled。
            return false;
        //设置成功
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                //当处于running状态,还要强行让他失败,那么只能中断interrupt了。
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();       //中断runner线程,也就是当前线程。
                } finally { // final state
                    //最后改变state值。
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

所以从上面代码看出,只有当state为NEW状态或者INTERRUPTING时候,调用cancel才有可能有效。

下面看看finishCompletion方法:

    /**
     * 完成计算。
     * 清除并unpack所有waiting threads,触发done。
     */
    private void finishCompletion() {
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);    //释放t。
                    }
                    WaitNode next = q.next;    //一直到结尾。
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
        done();
        callable = null;        // to reduce footprint
    }
get方法

get有两个方法,一个是有超时时间的,其中都是调用awaitDone方法:
即获取异步线程的执行的结果

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

看awaitDone方法:

    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {              
        final long deadline = timed ? System.nanoTime() + nanos : 0L;    //过期时间。   
        WaitNode q = null;            //一个waitNode。
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {           //如果当前线程已经interrupted了,那么就中断。
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {         //如果s完成了,就把q.thread为null,并且返回。
                if (q != null)
                    q.thread = null;
                return s;             //返回状态s
            }
            else if (s == COMPLETING) // cannot time out yet     //等待。正在计算
                Thread.yield();
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);   //超时挂起
            }
            else
                LockSupport.park(this);
        }
    }

由上述方法返回一个状态s,在有report判断返回,看report方法:

 @SuppressWarnings("unchecked")
    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);
    }

最后再来捋一捋FutureTask的思路,通过传入的Callable或者Runnable参数thread,最终转化为Callable,执行call方法,得到返回值。同时一个FutureTask里面实现了一个单向队列,但是这个单项队列并不是用来存储执行任务,我们知道FutureTask的get方法是阻塞性的,如果还没有计算出结果,那么就可能不会返回值,而是要当计算完成时候才会返回。
所以,当多个线程调用都调用get时候,此时如果还没有返回,那么就会把这些线程都阻塞起来,当完成后,在一个个把waiter都唤醒。

同时,FutureTask能够判定任务运行的状态,例如在构造方法中,state被设置为NEW

this.state = NEW;       // ensure visibility of callable

而在运行时,set方法里面,则又会设置state为COMPLETING,一旦完成,则会设置为NORMAL:

if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
     outcome = v;
     UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
     finishCompletion();
}

同时如果出错,则会设置为EXCEPTIONAL。
在cancel方法里面,一开始就会尝试更改state,如果更改成功,最终在finally块里面将state设置为INTERRUPTED:

if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))

参考资料:
1. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html
2. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html
3. https://www.jianshu.com/p/1dd951412ff5

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值