您的位置:首页 > 其它

再说Runnable、Callable、Future、线程池

2017-09-27 16:26 253 查看

Runnable接口

Runnable接口是关于线程的开发中使用最多的接口,在Handler中,我们可以post一个Runnable任务;我们经常使用的
Thread
也是Runnable接口的实现类


Runnable接口的run方法无返回值,也无异常抛出,也就是说我们在run方法中的任务一旦执行,我们无法获知任务是否执行完毕以及执行的结果。

源码

public interface Runnable {

public abstract void run();
}


Callable接口

Callable接口与Runable接口很类似,它也提供了一个类似
run
方法的
call
方法用于执行任务,但与
run
方法不同的是,
call
方法有返回值,且会抛出异常。

源码

public interface Callable<V> {

V call() throws Exception;
}


Future接口

Future接口与Runable接口和Callable接口都不一样,它没有类似的
run
方法或
call
方法,所以Future接口不能用来执行任务,并且Future接口设计出来的目的也不是为了执行任务,而是为了获取任务执行的结果,这一点从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;
}


Runable转为Callable

可以通过Executors类的
callable(Runnable task, T result)
静态方法,把一个Runable任务转换为Callable任务。

示例:

Callable<Object> callable = Executors.callable(new MyRunnable());


线程池(ExecutorService)

Executor源码:

public interface Executor {

void execute(Runnable command);
}


ExecutorService源码:

public interface ExecutorService extends Executor {

void shutdown();

List<Runnable> shutdownNow();

boolean isShutdown();

boolean isTerminated();

boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;

<T> Future<T> submit(Callable<T> task);

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

Future<?> submit(Runnable task);

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;

<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;

<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}


线程池被设计用来更好的控制与利用线程资源,节省线程的性能开销,提升线程执行效率。

由上述源码可知,Executor接口只有一个
void execute(Runnable command);
方法,只能用于执行Runable任务,不能执行Callable任务,所以为了能执行Callable任务,Java提供了ExecutorService接口,它继承于Executor,并扩展了一些方法,用来执行Callable任务,并且还提供了关闭线程等其他方法,所以在实际开发中我们一般都使用ExecutorService接口来做线程池的相关操作;

我们现在小小的总结一下,Java中的线程池可以执行哪几种任务:

- Runable任务

- Callable任务

- Collection任务,其实本质上还是Callable任务

一定要注意:Java线程池并不能执行Future任务,原因之前也说了,因为Future接口根本没有用于执行任务的方法,并且本来在设计上也不是用来执行任务的,而是用于获取任务执行的结果的。

submit方法与execute方法的区别

execute方法是Executor接口定义的,它只能用于执行Runable任务,并且无返回值。

submit方法是ExecutorService中定义的,它既能用于执行Runable任务,也可以用于执行Callable任务,并且必有返回值,且返回值为
Future<T>
,利用返回值
Future<T>
,我们可以获取任务执行的结果,以及中断任务的执行等操作。

因为execute方法无返回值,submit方法有返回值,所以如果一个任务不需要返回值,可以使用execute方法执行一个Runable任务,这样比较简单,而如果一个任务需要返回值,或者需要中断任务执行等操作,就需要使用submit方法,以实现更加精细的控制。 当然,不管需不需要返回值,都可以使用submit方法,看个人选择罢了。

线程池的使用

为方便使用,Java提供了Executors类,以静态工厂的方式提供各种类型的线程池,常见的有以下四种:

public static ExecutorService newCachedThreadPool();
public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newSingleThreadExecutor();
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);


上面每种线程池最终都实现了ExecutorService接口,所以它们都既可以执行Runable任务也可以执行Callable任务。如果Java提供的线程池不能满足需求,还可以通过
ThreadPoolExecutor
类自定义线程池。

下面以
newSingleThreadExecutor
为例演示线程池执行Runable任务和Callable任务的用法:

执行Runable任务

因为Runable任务无返回值,所以使用上相对比较简单。

使用execute方法

...
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
...
mExecutor.execute(new Runnable() {
@Override
public void run() {
//to do something
}
});


使用submit方法

...
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
...
mExecutor.submit(new Runnable() {
@Override
public void run() {
//to do something
}
});


执行Callable任务

对于Callable任务,只能使用submit方法

...
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
...
Future<String> future = mExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
String result = null;
//to do something
return result;
}
});


通过上述代码我们利用线程池执行了一个Callable任务,并返回一个
Future<String>
对象,利用
Future<String>
对象的
get
方法我们可以获取执行的结果:

Future<String> future = mExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
String result = null;
//to do something
return result;
}
});
try {
String result = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}


可以看到,我们在下方调用
future.get()
方法尝试获取任务的执行结果,这里我们要注意,如果此时任务还在执行中,
future.get()
方法会阻塞线程,一直阻塞到任务执行完毕返回时才释放,这种方式在Android开发中肯定是不行的,因为在Android的主线程中不能做耗时操作,否则就会发生ANR,所以我们在获取结果时,不能直接使用
future.get()
方法获取结果,除非我们知道这个任务已经执行完了,但是我们如何才能知道任务已经执行完毕了呢?虽然Future类中提供了
isDone
方法用来判断任务是否执行完毕,但是
isDone
是瞬时的方法,没法一直监听,难道我们还得写一个死循环一直查询任务是否执行完毕吗?No,No,No,我们有
FutureTask
类。

FutureTask类

FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口和Future接口,所以FutureTask类本质上是Runnable接口的实现类,且兼具Future接口的特性,我们知道线程池的
execute
方法和
submit
方法都可以执行Runable任务,所以同样可以执行FutureTask任务。

FutureTask类的构造方法如下:

public FutureTask(Callable<V> callable);
public FutureTask(Runnable runnable, V result);


看到了FutureTask类的构造方法,我们又知道了FutureTask既可以装载Runnable任务,又可以装载Callable任务。

最最最最关键的是FutureTask类有一个
done
方法,该方法在任务执行完毕时自动回调,我们可以重写该方法并在该方法中调用
get()
方法获取任务执行的结果,并且因为任务已经执行完毕,此时调用
get()
方法可以直接得到结果,所以并不会阻塞线程,简直太完美了。

上面也说了,FutureTask类本质上是Runnable的实现类,所以它本身并不能执行任务,一样要依靠
execute
方法或
submit
方法去执行任务。

示例代码

...
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
...
FutureTask<List<Integer>> task = new FutureTask<List<Integer>>(new MyCallable()) {
@Override
protected void done() {
try {
List<Integer> list = get();
//do something...
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};

mExecutor.execute(task);
//或者
//mExecutor.submit(task);


总结

通过这篇文章,我们搞懂了Runnable、Callable、Future之间的关系,以及如何使用线程池执行Runnable、Callable任务并获取执行结果,这对以后我们自己编写框架或者项目开发有着极大的帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程 线程池
相关文章推荐