转自:https://blog.csdn.net/qiang_xi/article/details/78114262
Runnable接口
Runnable接口是关于线程的开发中使用最多的接口,在Handler中,我们可以post一个Runnable任务;我们经常使用的Thread
也是Runnable接口的实现类。
Runnable接口的run方法无返回值,也无异常抛出,也就是说我们在run方法中的任务一旦执行,我们无法获知任务是否执行完毕以及执行的结果。
源码
public interface Runnable {
public abstract void run();
}
- 1
- 2
- 3
- 4
Callable接口
Callable接口与Runable接口很类似,它也提供了一个类似run
方法的call
方法用于执行任务,但与run
方法不同的是,call
方法有返回值,且会抛出异常。
源码
public interface Callable<V> {
V call() throws Exception;
}
- 1
- 2
- 3
- 4
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
Runable转为Callable
可以通过Executors类的callable(Runnable task, T result)
静态方法,把一个Runable任务转换为Callable任务。
示例:
Callable<Object> callable = Executors.callable(new MyRunnable());
- 1
线程池(ExecutorService)
Executor源码:
public interface Executor {
void execute(Runnable command);
}
- 1
- 2
- 3
- 4
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
线程池被设计用来更好的控制与利用线程资源,节省线程的性能开销,提升线程执行效率。
由上述源码可知,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);
- 1
- 2
- 3
- 4
上面每种线程池最终都实现了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
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 使用submit方法
...
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
...
mExecutor.submit(new Runnable() {
@Override
public void run() {
//to do something
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
执行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;
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
通过上述代码我们利用线程池执行了一个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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
可以看到,我们在下方调用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);
- 1
- 2
看到了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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
总结
通过这篇文章,我们搞懂了Runnable、Callable、Future之间的关系,以及如何使用线程池执行Runnable、Callable任务并获取执行结果,这对以后我们自己编写框架或者项目开发有着极大的帮助。