Android中Callable、Future、FutureTa
@author:小马快跑
@email:mqcoder90@gmail.com
@github:https://github.com/crazyqiang
学习线程池必备知识:
在开始介绍线程池之前,先来介绍下Callable和Future的概念,众所周知,Android中实现多线程的方式有两种,实现Runnable接口或者继承一个Thread,但是这两种方式都有一个缺点:在任务执行完成之后没有返回结果,所以在Java 1.5之后,出现了Callable和Future,通过他们构建的线程,可以在线程执行完成之后得到返回结果。
先来对比Runnable 和Callable:
Runnable :
public interface Runnable {
public abstract void run();
}
Callable<V>:
public interface Callable<V> {
V call() throws Exception;
}
Runnable 接口中的run()方法的返回值是void,所以Runnable无返回值;而Callable接口中的call()方法的返回值是V,也可以抛出异常,所以Callable是可以有返回值的。接着来看下Future:
Future<V>:
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;
}
Future<V>用来获取异步计算的结果,即是获取Callable<V>任务的结果
Future | 备注 |
---|---|
cancel(boolean) | 尝试取消异步任务的执行。如果任务已经执行完成、已经被取消、因为某种原因不能被取消,则返回false;如果任务正在执行,并且mayInterruptIfRunning为true,那么会调用interrupt()尝试打断任务。该方法返回结果后,isDone()总会返回true |
isCancelled() | 如果在任务完成前被取消,返回true |
isDone() | 如果任务完成则返回true。任务完成包括正常结束、任务被取消、任务发生异常,都返回true |
get() | 获取异步任务执行结果,如果没有返回,则阻塞等待 |
get(long timeout, TimeUnit unit) | 在给定的时间内等待获取异步任务结果,如果超时还未获取到结果,则会抛出TimeoutException |
Future<V>只是一个接口,还需要看Future的具体实现类:
future.png以FutureTask为例,具体分析下FutureTask<V>:
public class FutureTask<V> implements RunnableFuture<V> {
................其他.....................
}
咦?FutureTask并没有实现Future接口,而是实现了一个叫RunnableFuture的接口,这货从哪里蹦出来的?我们点进去看一下:
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
吆西!原来这货不仅继承了Future接口,还继承了Runnable接口(PS:接口可以多继承),那么我们的FutureTask也就实现了Runnable和Future接口
FutureTask | 备注 |
---|---|
boolean isCancelled() | 同Future |
boolean isDone() | 同Future |
boolean cancel(boolean mayInterruptIfRunning) | 同Future |
V get() | 同Future |
V get(long timeout, TimeUnit unit) | 同Future |
void run() | 如果这个任务没有被取消,将直接在当前线程内执行任务 |
FutureTask有两个构造方法:
public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable, V result)
我们看到FutureTask初始化时不仅可以传入Callable,还可以传入一个Runnable和一个"返回结果result",这里为什么返回结果要加引号呢,来看下源码就知道了:
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
上面FutureTask()的初始化构造参数中调用了Executors.callable():
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
private static final class RunnableAdapter<T> implements Callable<T> {
private final Runnable task;
private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
//把传入的值直接返回
return result;
}
}
通过源码我们看到,在最后的call()方法中,直接把传入的值返回了,所以FutureTask(Runnable runnable, V result)得到的只是预设的结果,Runnable并不会得到执行的结果。如果不需要预设的返回结果,可以像下面这样初始化:
Future<?> f = new FutureTask<Void>(runnable, null)
FutureTask的使用:
//初始化一个线程池
ExecutorService executor = = Executors.newSingleThreadExecutor();
//new 一个Callable并传入FutureTask
FutureTask<String> future =new FutureTask<>(new Callable<String>() {
public String call() {
//do something
return result;
}});
executor.execute(future);
//在异步任务执行期间可以做一些其他的事情
displayOtherThings();
//通过future.get()得到异步任务执行结果
String result=future.get();
ExecutorService :
ExecutorService继承自Executor接口,并提供了管理线程以及创建可以追踪一个或多个异步任务的进展的Future的方法。
public interface ExecutorService extends Executor {
//无法提交新任务,但是已经提交的任务将继续执行,当执行完成后关闭线程池
void shutdown();
//尝试停止所有正在执行的任务,暂停等待任务的处理,并返回等待执行的任务列表。
List<Runnable> shutdownNow();
//如果线程池已经关闭则返回true
boolean isShutdown();
//如果所有任务都在线程池关闭后完成,则返回true。注意,除非首先调用 shutdown 或 shutdownNow,否则 isTerminated 永不为 true。
boolean isTerminated();
//阻塞等待,直到所有任务在关闭请求后完成执行,或者超时发生,或者当前线程被中断
// 如果此执行程序终止,则返回 true;如果终止前超时了,则返回 false
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
//提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
//执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。
//返回列表的所有元素的 Future.isDone() 为 true。一旦返回后,即取消尚未完成的任务。
//注意,可以正常地或通过抛出异常来终止已完成 任务。如果此操作正在进行时修改了给定的
// collection,则此方法的结果是不确定的。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
//执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。
//一旦正常或异常返回后,则取消尚未完成的任务。如果此操作正在进行时修改了给定的 collection,
//则此方法的结果是不确定的。
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
线程池的使用:
使用线程池的好处:
1、当执行大量异步任务时,线程池能提供更好的性能体验,因为线程池能减少每个任务的调用开销,重用存在的线程,减少对象创建、消亡的开销
2、还可以提供绑定和管理资源 (包括执行任务时使用的线程) 的方法。有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
3、 提供定时执行、定期执行、单线程、并发数控制等功能。
Android中提供了四种线程池,四种线程池内部实现都是直接或者间接用的ThreadPoolExecutor。
1、Executors.newCachedThreadPool():只有Integer.MAX_VALUE个非核心线程,当有任务来时,如果线程池中的线程都处于活动状态,那么会新建线程来执行,否则就会利用空闲线程去执行,空闲线程都会有一个超时机制,超过60秒的空闲线程会被回收。任务队列为空集合,所以所有任务都会被立即执行,CachedThreadPool适合执行大量的耗时较少的操作。
效果图:
CachedThreadPool.gif
因为非核心线程数有无限个,所以不管有多少任务都可以并行执行,可以看到上述5个任务一起执行,核心代码:
//初始化线程池
ExecutorService threadPool= Executors.newCachedThreadPool();
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, what);
Thread.sleep(50);
}
return 100;
}
};
//执行线程任务
threadPool.submit(callable);
2、Executors.newFixedThreadPool(int nThreads):只有核心线程并且不会被回收,任务队列没有大小限制。
效果图:
FixedThreadPool.gif
因为核心线程数参数我们传入的是4,可所以看到先执行其中的4个任务,等待有任务执行完成后接着去执行第5个任务,核心代码:
//初始化线程池
ExecutorService threadPool= Executors.newFixedThreadPool(4);
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, what);
Thread.sleep(50);
}
return 100;
}
};
//执行线程任务
threadPool.submit(callable);
3、Executors.newSingleThreadExecutor():内部只有一个核心线程,所有任务按顺序执行 统一所有任务到一个线程中,使得这些任务不用处理线程同步问题。
效果图:
SingleThreadExecutor.gif
可以看到每次只能执行一个任务,也就是所有任务都是串行执行的,核心代码:
//初始化线程池
ExecutorService threadPool= Executors.newSingleThreadExecutor();
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, what);
Thread.sleep(50);
}
return 100;
}
};
//执行线程任务
threadPool.submit(callable);
4、Executors.newScheduledThreadPool(int corePoolSize): 核心线程是固定的,非核心线程是不固定的,非核心线程闲置时会被立即回收,主要用于执行定时任务和具有周期性的重复任务。
效果图:
Schedule.gif
ScheduledExecutorService传入的核心线程数是4,并且是在延迟2秒之后执行的,核心代码:
//初始化线程池,核心线程数为4
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(4);
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, what);
Thread.sleep(50);
}
return 100;
}
};
//延迟2秒后执行
scheduledPool.schedule(callable, 2, TimeUnit.SECONDS);
除上述延迟执行的方法外,ScheduledExecutorService中还有下列方法:
public interface ScheduledExecutorService extends ExecutorService {
//延迟delay(TimeUnit 为单位)之后执行Runnable
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
//延迟delay(TimeUnit 为单位)之后执行Callable
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
//延迟initialDelay之后每period时间执行一次Callable,循环执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
//第一次延迟initialDelay之后执行,之后在每次完成后延迟delay时间执行下一次操作
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
本文例子中完整代码已上传github:Android线程池的使用