Runnable和Callable用法解析

编程

Runnable

概述

众所周知,在Java中创建线程的2种方式是,继承thread重写run方法,实现Runnable接口。你可曾听闻有第三种方式叫做callable?先来探讨一下前两种方式是怎么实现的。Runnable接口是对异步任务的抽象,Thread对象实现了Runnable接口,并提供了多个构造方式用来创建线程,以下是Runnable接口的源码和Thread类的部分源码。

@FunctionalInterface

public interface Runnable {

public abstract void run();

}

public class Thread implements Runnable {

private Runnable target;

public Thread(Runnable target) {

init(null, target, "Thread-" + nextThreadNum(), 0);

}

@Override

public void run() {

if (target != null) {

target.run();

}

}

public synchronized void start() {

boolean started = false;

try {

start0();//native method

started = true;

}

}

//忽略其余代码

}

用法示例

通过thread的源码,我们可以得知:

  • 在构造方式传入Runnable对象,调用start()方法可以创建线程,这种方式使用了默认的run()方法,并调用target的run()方法。

    new Thread(new Runnable() {

@Override

public void run() {

}

}).start();

  • 通过继承Thread重写run()方法,调用start()方法可以创建线程,这种方式省去了把异步代码委托在Runnable对象中。

    new Thread(){

@Override

public void run() {

}

}.start();

Callable

/**

* A task that returns a result and may throw an exception.

* Implementors define a single method with no arguments called

* {@code call}.

*

* <p>The {@code Callable} interface is similar to {@link

* java.lang.Runnable}, in that both are designed for classes whose

* instances are potentially executed by another thread. A

* {@code Runnable}, however, does not return a result and cannot

* throw a checked exception.

*

* <p>The {@link Executors} class contains utility methods to

* convert from other common forms to {@code Callable} classes.

*

* @see Executor

* @since 1.5

* @author Doug Lea

* @param <V> the result type of method {@code call}

*/

@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也是一种类似Runnable的异步任务抽象,但是可以返回结果,或抛出异常的,也属于jdk1.5版本加入的Executor任务执行模块的一部分。我们知道Runnable是配合Thread类使用的,那么Callable呢?从Thread类源码中,并没有看到接受callable接口的参数,所以callable的不会与Thread直接联系使用,答案是FutureTask,FutureTask包含传入Callable和Runnable接口的构造方法,及判断是否执行完成,取消任务等接口,get()方法用于获取结果,并且是阻塞的,也就是说任务没有执行完成会一直等待。下面是FutureTask的继承关系和方法列表:

使用场景

FutureTask可用于异步任务获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接结合Thread对象或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。比如在需要聚合接口的场景,可以通过FutureTask并行远程调用多个接口,最后在主线程汇总结果返回给前端。

用法示例

最简单的用法

    public void test1() {

FutureTask<String> stringFutureTask = new FutureTask<>(() -> {

Thread.sleep(1000);

return "200 ok";

});

new Thread(stringFutureTask).start();

try {

System.out.println("等待获取结果");

System.out.println(stringFutureTask.get());

} catch (ExecutionException | InterruptedException e) {

e.printStackTrace();

}

}

等待获取结果

200 ok

配合线程池用法

public void test2() throws InterruptedException {

ExecutorService executorService = Executors.newFixedThreadPool(10);

List<Future> futureTasks = new ArrayList<>();

for (int i = 0; i < 10; i++) {

final int taskNum = i;

//异步批量提交任务执行

Future<String> task = executorService.submit(new Callable<String>() {

@Override

public String call() throws Exception {

Thread.sleep(1000);//模拟任务执行

return "200 ok" + taskNum;

}

});

//保存结果钩子

futureTasks.add(task);

System.out.println("任务" + taskNum + "添加到队列。");

}

//模拟主线程执行

Thread.sleep(2000);

futureTasks.forEach(future -> {

try {

//获取异步任务结果

System.out.println(future.get());

} catch (InterruptedException | ExecutionException e) {

e.printStackTrace();

}

});

}

任务0添加到队列。

任务1添加到队列。

任务2添加到队列。

任务3添加到队列。

任务4添加到队列。

任务5添加到队列。

任务6添加到队列。

任务7添加到队列。

任务8添加到队列。

任务9添加到队列。

200 ok0

200 ok1

200 ok2

200 ok3

200 ok4

200 ok5

200 ok6

200 ok7

200 ok8

200 ok9

使用CompletionService

public static void test3() throws InterruptedException, ExecutionException {

ExecutorService executorService = Executors.newFixedThreadPool(10);

ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService);

for (int i = 0; i < 10; i++) {

final int taskNum = i;

completionService.submit(() -> "200 ok" + taskNum);

}

for (int i = 0; i < 10; i++) {

Future<String> take = completionService.take();

System.out.println(take.get());

}

}

200 ok1

200 ok0

200 ok2

200 ok3

200 ok4

200 ok5

200 ok6

200 ok7

200 ok8

200 ok9

以上是 Runnable和Callable用法解析 的全部内容, 来源链接: utcz.com/z/518051.html

回到顶部