Future是什么
在使用线程的时候,我们会理所当然的想到继承Thread或者实现Runnable,但这两种方式都存在着一个问题,无法返回执行结果。并且,继承Thread也会带来一个破坏面向对象原则的问题,即不符合里氏替换原则。
为了解决此类问题,JDK1.5后引入了Future
和Callable
。
Callable和Future
首先,我们来观察一个Callable
接口,它是一个支持泛型并且函数式编程的接口,在使用时我们仅需要实现它的call()
方法。
其次,我们再来看一下Future
接口,各方法解释已写入代码注释中。
public interface Future<V> {
/**
* 试图中断线程。
* 1、如果任务已经执行完毕(completed)或者已经被取消(canceled),
* 又或者是因为一些其他的原因,任务不能被中断,则该方法会执行失败。
* 2、如果在任务还没启动并且调用该方法成功的情况下,任务将永远无法执行。
* 3、如果任务已经启动,参数mayInterruptIfRunning可用于决定是否尝试中止任务
* 4、当该任务返回了,isDone()方法会永远返回true,因为如果该方法返回false,则说明线程已经执行完成或者被中断,
* 如果返回true,则说明线程被当次调用中止成功了,但不论哪种,线程都是已经停止了的情况。
* 5、如果方法返回true,则isCanceled()方法返回true。
*/
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
/**
* 如果任务完成(不论正常或者异常)则返回true。
*/
boolean isDone();
/**
* 等待任务执行完成,然后获取它的结果
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
V get() throws InterruptedException, ExecutionException;
/**
* 如果在限定时间内线程未执行完毕,则中止(抛出TimeOutException),否则返回结果。
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
使用示例
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 8, 3000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
private ThreadPool(){}
public static ThreadPoolExecutor getInstance() {
return executor;
}
}
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
public class Job implements Callable<Integer> {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(3);
System.out.println("[" +Thread.currentThread().getName() + "] keep calculation! Current time : " + System.currentTimeMillis());
AtomicReference<Integer> count = new AtomicReference<>(0);
IntStream.range(1, 1000).forEach(item -> count.updateAndGet(v -> v + item));
return count.get();
}
}
import java.util.concurrent.*;
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Job task = new Job();
ThreadPoolExecutor executor = ThreadPool.getInstance();
Future<Integer> taskFuture = executor.submit(task);
executor.shutdown();
TimeUnit.SECONDS.sleep(2);
System.out.println("[ " + Thread.currentThread() + " ] keep running! Current time : " + System.currentTimeMillis());
System.out.println(taskFuture.get());
System.out.println("all stop");
}
}
运行结果
[ Thread[main,5,main] ] keep running! Current time : 1599973063008
[pool-1-thread-1] keep calculation! Current time : 1599973064009
499500
all stop
主线程的时间是1599973063008,job的时间是1599973064009,两者相差1秒。而程序中,主线程是sleep了2秒,job是sleep了3秒。从这可以说明Future对象是一直在异步执行的。
可以再验证一下:
import java.util.concurrent.*;
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Job task = new Job();
ThreadPoolExecutor executor = ThreadPool.getInstance();
Future<Integer> taskFuture = executor.submit(task);
executor.shutdown();
// TimeUnit.SECONDS.sleep(2);
System.out.println("[ " + Thread.currentThread() + " ] keep running! Current time : " + System.currentTimeMillis());
System.out.println(taskFuture.get());
System.out.println("all stop");
}
}
运行结果:
[ Thread[main,5,main] ] keep running! Current time : 1599976879324
[pool-1-thread-1] keep calculation! Current time : 1599976882325
499500
all stop
主线程是1599976879324,job是1599976882325,两者相差了3s,再次验证了上文的结论。
再查看ThreadPoolExecutor
的commit()
方法时,会看到有涉及到RunnableFuture这个接口。
跟踪newTaskFor()
方法,会发现一个新的类,FutureTask
。
FutureTask
实现了RunnableFuture
接口,并且提供了多个构造方法,如果是Callable,则直接将其赋给callable属性,而如果是Runnable的话,会再涉及到Executors.callable
这个方法。
跟踪Executors.callable
方法,会涉及到一个新的RunnableAdapter
类。
接下来我们分析一下这几个类的关系。
在这其中,FutureTask
利用RunnableAdapter
对Runnable
接口转换为了Callable
,达到了一个适配的效果,FutureTask
又引用了Callable
属性,就此形成了对Callable
接口和Runnable
接口的支持。
同时,观察ThreadPoolExecutor
的所有commit()
方法,你会发现,无论是Runnable
还是Callable
,最终都配转化为FutureTask
,这样也达到了一个统一处理的效果。