线程大家都很熟悉,一般在做耗时操作的时候,我们会开启一个线程来帮我们完成耗时任务,这样就可以避免主线程阻塞,提高用户体验。
但是在使用线程的时候有一个问题,线程本身执行情况我们是无法获取的。一般我们会加一个接口之类的在线程完成后回调。其实这个问题我们也可以使用Callable和Future来解决。
Callable
@FunctionalInterface
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;
}
以上是Callable的源码,可以看到Callable是一个接口,里面只有一个方法call(),并且带有返回参数。我们从这个类的解释可以看出来,它是一个可以返回结果或者抛出异常的任务,解决了Runnable没有返回值等问题,可以结合Executors一起来使用。
在运用时我们一般会写一个Callable的实现类,然后再call()方法中完成耗时任务。
private static class TestCallAble implements Callable {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "finish";
}
}
接下来我们看Executors中执行Callable中的耗时任务的方法。
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
我们先来看第一个方法,这里传入Callable,我们一直跟到源码,最终我们发现执行了这个方法。
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
方法中先是做了一个null的判断,然后调用newTaskFor(),这里实际上是返回了一个FutureTask类,FutureTask实现了RunnableFuture接口。而RunnableFuture继承了Runnable和Future。最后再调用execute()方法来异步执行任务。
接着我们看第二个方法
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
我们看到第二个方法传入了一个Runnable和一个返回值result。进入submit方法后我们发现这里和传入callable时调用的方法基本一样,只在在newTaskFor()的时候多传入了返回值result。我们继续跟进去,最后在FutureTask类中我们发现在初始化FutureTask的时候调用了Executors.callable(task, result)方法。
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
我们进入Executors并找到源码,我们发现返回值是RunnableAdapter。RunnableAdapter是实现了Callable,在call()方法中执行了Runnable的run方法,并在最后返回了传入的返回值result。讲到这里大家就明白了这里只是把传入的Runnable经过一系列转换变成了实现Callable接口的RunnableAdapter类。
第三个方法和第二个方法差不多,只是第三个方法返回值为null,具体大家可以自己参看源码,这里就不分析了。
Future
public interface Future<V>
我们可以看到Future是一个接口,上面方法中也都返回了参数Future。
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
接口中有这5个方法:
cancel(boolean mayInterruptIfRunning) 是取消任务,方法中需要传入一个布尔值。意思是当任务正在执行的时候是否取消,如果是true,那么任务就算是执行中也会取消。是否取消成功会有一个返回值。
isCancelled(),isDone() 相信大家看名字就可以知道作用。
get() 是获取返回值,需要注意的是这个方法会等到任务执行完成后才会回调返回值,然后继续执行下面的代码。
get(long timeout, TimeUnit unit) 传入了时间,这里是在限制时间内获取返回值,如果传入的时间到了任务还没有执行完成,会抛出异常。
执行完submit方法后实际返回的是FutureTask类,FutureTask中实现了接口Future的所有方法。所以我们在调用方法的时候是执行FutureTask中的方法。
方法的具体实现这里不做分析,我就简单说说get()的原理,在FutureTask类中get()其实就是一个for循环,一直在循环等待线程中的任务完成,任务完成后,在将外部传入的返回值(result)返回出去。肯定有人会有疑问这样难道就不阻塞线程吗?如果没有其他操作这样确实会阻塞线程,但是在源码中我们可以看到有这样的代码:
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
这样大家就明白了为什么它一直循环等待也不会有阻塞问题了吧。
实际运用:
public class Main {
public static void main(String[] args) {
//执行方法1
ExecutorService pool = Executors.newCachedThreadPool();
Future future = pool.submit(new TestCallAble());
try {
System.out.println("result = " + future.get());
} catch (Exception e) {
e.printStackTrace();
}
//执行方法2
FutureTask<String> task = new FutureTask<String>(new TestCallAble());
Thread thread = new Thread(task);
thread.start();
try {
System.out.println("result = " + task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
private static class TestCallAble implements Callable {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "finish";
}
}
}
这个例子很简单,就是在call()中延时3秒来模拟任务,然后分别用两个方法来执行这个任务,得出的结果是一样的(实际运用中建议用方法1来执行任务)。
如果上文中有错误的地方希望大家指出,我好及时修正,其它问题也可以和我交流。
网友评论