在Java5以后,通过Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还能使用线程池的特性。上一篇我们介绍了线程池的基本用法和特性。我们用的最多的是ExecutorService的execute方法,它帮我们把线程任务交给线程池,剩下就不管了,也不关心返回值。那么如果我们向把任务交给线程池处理,同时又期望得到结果,这时候怎么做呢?
0.异步返回值的场景
我们先不用线程池,来实现一个功能。加入周末了,几个小伙伴要一块做饭,假设做饭需要3个步骤,分别是:
1.打扫厨房卫生,准备厨具
2.买菜
3.炒菜
现在我们怎么分工呢,现实中一般是这样的,一部分小伙伴去买菜,一部分小伙伴打扫准备厨具,等这两样都做完之后才炒菜。也就是说第1步和第2步是同时进行的。
我们使用Java代码来实现下:
public class Cook{
public static void futureCook() throws ExecutionException, InterruptedException {
Callable<Food> first = new Callable<Food>() {
@Override
public Food call() throws Exception {
//do something
return new Food();
}
};
FutureTask<Food> task1 = new FutureTask<Food>(first);
new Thread(task1).start();//先去做第一步
Object task2 = null;
//.... 第二步做了一些事
// 第三步 需要用到第一步和第二部的结果
if (!task1.isDone()) { // 第一步是否完成
System.out.println("task1 is done");
}
Food foods = task1.get();//获取第一步的结果
cook(foods, task2);
}
private static void cook(Food foods, Object task2) {
}
static class Food{
Object food;
}
}
我们先用一个线程去买菜,执行第一步,同时执行第二步,第二步完成之后,我们试图获取第一步的结果,如果第一步没有执行完,那么就阻塞在这里等待,直到完成。前两步完成之后,我们就可以愉快的做饭了。
下面我们分析下使用到的技术
1.Callable
Callable是一个接口,它只有一个方法call,调用call可以异步执行,并返回结果。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
2.FutureTask
在不用线程池的情况下,我们使用Callable来执行任务,使用Future来获取任务结果,FutureTask实现了RunnableFuture接口:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
具体的步骤如下:
1.创建对象实现Callable接口,实现其call方法
2.使用Callable对象创建FutureTask对象
3.执行使用FutureTask创建线程执行
4.FutureTask获取执行结果
Callable<Object> c= new Callable<Object>() {
@Override
public Objectcall() throws Exception {
return new Object();
}
};//1
FutureTask<Object> task = new FutureTask<Object>(c);//2
new Thread(task ).start();//3
Object obj = task.get();//4
这是没有用线程池ThreadPoolExecutor的情况下,异步返回值的最简单实现。
下面我们看下在线程池中是如何操作的
1.ThreadPoolExecutor的submit的使用场景
上面的例子我们知道了,如果想要异步执行得到返回值,就需要使用Callable。在线程池ExecutorService接口中,我们如果异步执行得到返回值呢?
ExecutorService的接口定义:
ExecutorService{
void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
}
其中execute方法是继承Executor的,只接受Runnable的参数,并且无返回值。
submit方法可以接收Callable和Runnable的参数,并且有返回值Future。具体的使用方法会在后面讲
Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
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;
}
-
cancel方法用来取消任务。如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false.
-
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
-
isDone方法表示任务是否已经完成,若任务完成,则返回true;
-
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
-
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
Future是一个接口,真正完成功能的是FutureTask,它的基本用法我们上一节已经说明了,我们看下它的实现:
public class FutureTask{
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
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();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
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);
}
}
}
FutureTask内部定义了一个Callable变量。执行异步任务的时候,还是执行run方法。
1.判断任务的状态,如果任务不是初始状态(NEW),则返回
2.result = c.call();调用Callable的call方法,保存返回值
3.如果执行c.call()抛出异常,则保存异常
4. set(result);保存执行结果,更新任务状态
实践
public static void main(String[] args) {
submitTest();
}
private static ExecutorService executorService = null;
public static class CallRunner implements Callable<Counter>{
@Override
public Counter call() throws Exception {
Counter counter = new Counter();
for (int i=0;i<10000;i++){
counter.i++;
}
System.out.println("CallRunner 执行结束");
return counter;
}
}
public static void submitTest(){
executorService = createThreadPool();
Future<Counter> future = executorService.submit(new CallRunner());
executorService.execute(new T(2));
try {
Counter counter = future.get();
System.out.println("count = "+counter.i);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private static ThreadPoolExecutor createThreadPool() {
int corePoolSize = 3;
int maximumPoolSize = 5;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(20);
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.MINUTES, workQueue);
return executor;
}
public static class Counter{
int i = 0;
}
public static class T implements Runnable{
@Override
public void run() {
System.out.println("Runnable 执行完毕");
}
}
上面的例子中我们使用 Future submit(Callable task)这个接口:
Future future = executorService.submit(new CallRunner());
在看下这3个接口,
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
Future submit(Runnable task, T result)怎么使用呢?很简单,我们定义一个对象result,然后当做第二个参数传入submit方法中。在call中将结果设置到result中。这样就不用Future.get()方法获取结果了。当然这样用的很少,具体看使用场景了。
Future<?> submit(Runnable task)这个接口有什么用的?我们知道Runnable 的run方法是没有返回值的,但是仍然返回Future。虽然不要发返回值,但是我们可以用Future获取异步任务的状态啊,是否完成、取消;get()方法阻塞直到任务执行完
2.execute 和 submit的区别
看了上面的例子,就容易理解execute 和 submit的区别了。
1.参数不同
execute只能接收Runable参数
submit接收Runable,Callable
2.返回值不同
execute没有返回值,
submit返回Future对象,可以查询任务的执行状态和执行结果
3.异常处理不同
execute中的是Runnable接口的实现,execute中出现的异常,会被直接抛出来,或者只能在run方法中捕获。
submit后出现的异常,可以在call方法中捕获处理,也可以在外部处理。而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。如果不调用Future.get方法,则即便有错误异常,也不会抛出。