java并发:获取线程执行结果(Callable、Future、FutureTask)

初识Callable and Future

在编码时,我们可以通过继承Thread或是实现Runnable接口来创建线程,但是这两种方式都存在一个缺陷:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到目的。

Java5提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。

Callable and Future源码

(1)Callable接口

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

(2)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;
}

源码解说:

(1)Callable位于java.util.concurrent包下,它是一个接口,在它里面只声明了一个call()方法。从上面的源码可以看到,Callable是一个泛型接口,call()函数返回的类型就是传递进来的泛型实参类型。

(2)Future类位于java.util.concurrent包下,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果,其cancel()方法的参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置为true,则表示可以取消正在执行过程中的任务;get()方法用来获取执行结果,该方法会阻塞直到任务返回结果。

 

补充:

cancel ⽅法是试图取消⼀个线程的执⾏,并不⼀定能取消成功;因为任务可能已完成、已取消、或者⼀些 其它因素不能取消,存在取消失败的可能。

 

Callable and Future示例

(1)下面的示例是一个Callable,它会采用最明显的方式查找数组的一个分段中的最大值。

import java.util.concurrent.Callable;

class FindMaxTask implements Callable<Integer> {

  private int[] data;
  private int start;
  private int end;
  
  FindMaxTask(int[] data, int start, int end) {
    this.data = data;
    this.start = start;
    this.end = end;
  }

  public Integer call() {
    int max = Integer.MIN_VALUE;
    for (int i = start; i < end; i++) {
      if (data[i] > max) max = data[i];
    }
    return max;
  }
}

(2)将Callable对象提交给一个Executor,它会为每个Callable对象创建一个线程,如下代码段所示:

import java.util.concurrent.*;

public class MultithreadedMaxFinder {

  public static int max(int[] data) throws InterruptedException, ExecutionException {
    
    if (data.length == 1) {
      return data[0];
    } else if (data.length == 0) {
      throw new IllegalArgumentException();
    }
    
    // split the job into 2 pieces
    FindMaxTask task1 = new FindMaxTask(data, 0, data.length/2);
    FindMaxTask task2 = new FindMaxTask(data, data.length/2, data.length);
    
    // spawn 2 threads
    ExecutorService service = Executors.newFixedThreadPool(2);

    Future<Integer> future1 = service.submit(task1);
    Future<Integer> future2 = service.submit(task2);
        
    return Math.max(future1.get(), future2.get());
  }
}

 

补充:

ExecutorService接口中声明了若干个不同形式的submit()方法,各个方法的返回类型为Future类型,如下:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

 

初识FutureTask

因为Future只是一个接口,所以是无法直接用来创建对象来使用的,因此就有了FutureTask。

FutureTask目前是Future接口的唯一实现类,FutureTask表示一个可以取消的异步运算,它有启动和取消运算、查询运算是否完成和取回运算结果等方法;只有当运算完成的时候才能取回结果,如果运算尚未完成,则get方法将会阻塞。

FutureTask作为线程安全的一次性任务容器,其设计融合了任务封装、状态隔离、并发控制三大核心能力。

 

image

 

接口定义

FutureTask实现了RunnableFuture接口,其声明如下:

public class FutureTask<V> implements RunnableFuture<V>

RunnableFuture接口定义如下:

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

解说:

因RunnableFuture接口继承Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口,所以FutureTask可以通过如下两种方式运行以获取结果:

  • 作为Runnable被线程执行(Thread接收Runnable类型的参数)
  • 提交给Executor执行(ExecutorService.submit(Runnable task))

FutureTask构造函数

FutureTask的构造函数接收不同形式的参数,如下:

    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param  callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

简单示例:

// 封装 Callable
FutureTask<String> task = new FutureTask<>(() -> {
    return "计算结果";
});

// 封装 Runnable(通过 result 传递结果)
FutureTask<String> task = new FutureTask<>(() -> {
    System.out.println("执行任务");
}, "固定结果");

 

任务状态

FutureTask 的内部有一个变量 state 用来表示任务的状态,相关定义如下:

    /**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    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;

解释:

FutureTask 通过 volatile 变量 state 记录任务状态,所有操作均围绕状态流转展开

状态机设计确保状态单向迁移(如 NEW → COMPLETING),无法回退

状态流转规则

仅 NEW → COMPLETING → NORMAL/EXCEPTIONAL 表示任务执行成功
状态一旦离开 NEW,任务便不可能再次执行(参见后文FeatureTask原理run方法

 

FutureTask示例

观察下述两个示例代码中FutureTask的使用方式

示例一

FutureTask将被作为Runnable被线程执行

(1)任务线程ThreadC:

package demo.thread;
import java.util.concurrent.Callable;
//实现Callable接口,call()方法可以有返回结果
public class ThreadC implements Callable<String> {
      @Override
      public String call() throws Exception {
            try {//模拟任务,执行了500毫秒;
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "thread B";
      }
}

(2)主线程ThreadMain:

package demo.thread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadMain {
      public static void main(String[] args) {
            FutureTask<String> feature = new FutureTask<String>(new ThreadC());
            new Thread(feature).start();//注意启动方式,FutureTask将被作为Runnable被线程执行
            
       System.out.println("这是主线程;begin!");
            //注意细细体会这个,只有主线程get了,主线程才会继续往下执行
            try {
                System.out.println("得到的返回结果是:"+feature.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("这是主线程;end!");
      }
}

 

示例二

FutureTask被提交给Executor执行以得到返回值

public class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Thread.sleep(3*1000);
        int sum = 0;
        for(int i=0;i<100;i++)
            sum += i;
        return sum;
    }
}

 

public class Test {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Task());
        executor.submit(futureTask);//FutureTask被提交给Executor执行以得到返回值
        executor.shutdown();
         
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
         
        System.out.println("主线程在执行任务");
         
        try {
            System.out.println("task运行结果"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
         
        System.out.println("所有任务执行完毕");
    }
}

 

FutureTask原理

FutureTask 能在高并发环境下确保任务仅执行一次,其关键在于内部实现了线程安全的状态机和原子性控制机制。

典型应用场景

class AtomicCache<K, V> {
    private final Map<K, FutureTask<V>> cache = new ConcurrentHashMap<>();
    
    public V get(K key) throws ExecutionException {
        FutureTask<V> task = cache.computeIfAbsent(key, k -> 
            new FutureTask<>(() -> computeExpensively(k)));
        task.run(); // 确保并发下仅执行一次计算
        return task.get();
    }
}

 

高并发场景下的行为

1. 首次执行成功

线程A:通过 CAS 成为 runner → 执行任务 → 更新状态为 NORMAL
线程B:检测到 state != NEW → 直接返回

2. 执行中取消任务

线程A:正在执行任务
线程B:调用 cancel(true) → 通过 CAS 将状态改为 INTERRUPTING
线程A:检测到状态变化 → 响应中断 → 最终状态变为 INTERRUPTED

3. 重复调用 run()
若多个线程同时调用 run():

仅一个线程通过 CAS 成为 runner
其余线程因状态检查失败或 CAS 竞争失败而退出

 

并发控制:CAS + 自旋锁

任务执行入口(run()方法)

public void run() {
    if (state != NEW || // 状态检查
        !RUNNER.compareAndSet(this, null, Thread.currentThread())) // CAS抢占执行权
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) { // 二次状态检查(Double-Check)
            V result;
            boolean ran;
            try {
                result = c.call(); // 实际执行任务
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex); // 异常状态转换
            }
            if (ran)
                set(result);      // 正常状态转换
        }
    } finally {
        runner = null; // 释放执行线程引用
        if (state == INTERRUPTING) // 处理中断
            handlePossibleCancellationInterrupt();
    }
}

解读:

非NEW状态直接退出

关键保护点:

CAS 抢占:通过 compareAndSet 确保仅一个线程成为执行者(runner)
状态双重检查:防止执行权抢占后状态被其他线程修改

状态变更的原子性

无锁化状态跃迁:状态转换通过 UNSAFE.compareAndSwapInt 实现原子操作

set()方法

protected void set(V v) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) { // 原子状态跃迁
        outcome = v;
        STATE.setRelease(this, NORMAL); // 最终状态,内存语义:写后可见
        finishCompletion(); // 唤醒等待线程
    }
}

解读:

任务结果(outcome)一旦设置,即被**final 语义保护** —— 非volatile,依赖状态机保证可见性

任务完成后调用 finishCompletion(),自旋唤醒所有等待线程

 

get()方法

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

/**
 * @throws CancellationException {@inheritDoc}
 */
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);
}

 

/**
 * Awaits completion or aborts on interrupt or timeout.
 *
 * @param timed true if use timed waits
 * @param nanos time to wait, if timed
 * @return state upon completion
 */
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return 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); // CAS入队
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this); // 最终挂起
    }
}

解读:自旋优化 —— 短时自旋避免立即挂起线程

 

/**
 * Returns result or throws exception for completed task.
 *
 * @param s completed state value
 */
@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);
}

等待机制:

未完成的任务会构建一个 WaitNode 链表(通过 CAS 追加节点)

 

FutureTask小结

与同步原语的性能对比

image

 

设计模式

前面提到了一条重要信息:ExecutorService接口中的submit()方法可以接收callable、Runnable类型的参数,方法的返回类型为Future类型。

ExecutorService的submit()方法的内部实现是根据参数构建了FutureTask对象,然后将FutureTask对象转为Future类型返回。

这也对应了下面这条信息:

FutureTask间接继承了Future接口,其构造函数可以接收callable、Runnable类型的参数。

Note:

仔细想一想,其实这个内部实现使用了适配器模式,使得不同接口的实现最终对外表现为一致

posted @ 2016-03-17 15:19  时空穿越者  阅读(9915)  评论(0)    收藏  举报