再说Runnable、Callable、Future、线程池

转自: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任务并获取执行结果,这对以后我们自己编写框架或者项目开发有着极大的帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值