在单线程程序中,程序都是同步执行(Synchronous Execution)的。即:当生成一个任务,并开始执行后,主线程必须等待任务执行完毕之后,才能执行下一个任务。然而,在多线程环境下,因为两个或者多个线程可以并行执行,这也就产生了另一种执行方式:异步执行(Asynchronous Execution)。
异步执行(Asynchronous Execution)方式是指当一个任务生成后,主线程可以将其提交到另一个线程上去执行。与此同时,主线程不必等待该任务的运行,主线程可以立即着手去做另一项任务。等过一段时间后,主线程可以检查提交的任务是否已执行完毕。如已完毕,主线程可以获得任务的结果。这种运行方式被称为Java异步执行方式。这种方式是由Callable和Future接口实现的。
异步执行方式被广泛的用于大型应用程序中。Web服务器和图形用户界面程序(GUI Application)是两个较为典型的例子。在这两个例子中,当用户提交一个计算量较大的任务时,如果该任务是同步运行的话,只有当任务结束之后,应用程序才将结果返回给用户。因此,在这个过程中,用户需要等待较长的时间才能直到结果。然而,如果使用了异步执行的方式,当任务提交后,系统可以给用户先返回一个任务提交成功的消息。用户看到此消息后,可以转去做其他的事情,不必一直守候在系统前。任务的结果可以稍后来取。因此,实际上,异步执行方式提供了更加友好的服务,节省了用户的等待时间。
Callable和Future是Java标准库为实现异步执行而设计的两个重要概念。Callable和Future是两个接口。Callable只包含一个方法call();Future定义了一系列方法用户检查任务是否执行成功,以及获取执行结果。Callable和Future都是在java.util.concurrent包中。
Callable接口用于描述一项任务。开发人员需要在具体类中实现Callable接口;即通过覆盖call()方法,描述任务的内部逻辑。call()方法执行的结果由返回对象描述。
Callable接口和Runnable接口十分相似,我们在下面简单的总结一下它们的相同点和不同点。
与Runnable类似,Callable对象可由以下三种方式创建。
第一种方法是创建一个新类,实现Callable接口。这是最为常见的一种方法。开发人员可以自定义一个新类,并实现Callable接口。例如:下面的CallableTaskExample类实现了Callable接口,在call()方法中返回一个字符串"This is running in a new thread"。因为Callable是一个泛型接口,其接收的泛型类就是call()方法返回的对象类型。因此,在下例中,该泛型类类型是String。
import java.util.concurrent.Callable;
public class CallableTaskExample implements Callable<String> {
@Override
public String call() {
return "This is running in a new thread";
}
}
第二种方法是使用匿名类,实现Callable接口。当任务的执行逻辑较为简单时,使用匿名类是一个不错的选择。在如下的例子中,并没有定义一个全新的类来表示新线程的运行逻辑。Java虚拟机会为其自动创建一个匿名类。
public class AnonymousCallableTaskExample {
public static void main(String[] args) {
// 创建一个匿名类,实现Callable接口
Callable<String> anonymousTask = new Callable<String>() {
@Override
public String call() {
return "This is running in a new thread";
}
};
}
}
第三种方法是使用Lambda表达式实现Callable接口。如果还想省略更多的代码,那么,可以使用Lambda表达式来创建一个Callable接口的对象。因为Callable接口只提供了一个方法call()。因此,Callable接口也是一个Functional Interface。所以,Lambda表达式也可以转换成Callable接口的对象。(注:Java 8以上版本才支持Lambda表达式和Functional Interface。)
public class LambdaExpressionCallableTaskExample {
public static void main(String[] args) {
// 使用Lambda表达式创建一个Callable对象
Callable<String> lambdaExpressionTask =
() -> {return "This is running in a new thread";};
}
}
java.util.concurrent.Future接口表达的是一个异步执行的任务的结果。这项任务可能正在执行,也可能已经执行完毕。Future接口提供了一系列的方法,允许开发人员检查任务的状态;若已执行完毕,可以获取任务的结果;若尚在运行,开发人员可以选择取消这项任务。
下面是一个使用Callable和Future的例子。例子中使用了ExecutorService类。它是一个线程池,可以接受Callable对象,并以异步的方式运行这个Callable对象。在使用submit()方法提交Callable对象的时候,submit()方法会立即返回一个Future对象。主线程可以使用这个Future对象来跟踪和查看该任务的执行状态和结果。
Callable对象的执行结果可由Future.get()方法返回。如果任务尚在执行中,Future.get()方法会阻塞当前线程,直到任务执行完毕,并能获取任务的执行结果。如果任务已执行完毕,Future.get()会立即返回执行结果。
Future.get(long, TimeUnit)是获取结果的第二种形式。这个方法允许开发人员提供一个最长的等待时间。如果任务尚在执行,并且等待的时间超过了最长等待时间的话,Future.get(long, TimeUnit)方法会抛出Timeout异常。
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FutureExample {
public static void main(String[] args) {
// 创建一个Callable对象,计算一个整数的平方和三次方,
Integer i = 10;
Callable callable1 = () -> {return i * i;};
Callable callable2 = () -> {return i * i * i;};
// 创建一个单线程的线程池,将Callable对象提交给线程池执行
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future1 = executor.submit(callable1);
Future<Integer> future2 = executor.submit(callable2);
// 循环等待计算结束
while(!future1.isDone()) {
System.out.println("Calculating...");
Thread.sleep(1000);
}
// 获取异步执行的结果
Integer result1 = future1.get();
System.out.println("The result is " + result1);
for (;;) {
Integer result2 = null;
try {
// 尝试获取结果,并最多等待1秒的时间
result2 = future2.get(1, TimeUnit.SECONDS);
} catch (TimeoutException ex) {
// 如果等待超时,则会抛出TimeoutException异常
continue;
}
System.out.println("The result is " + result2);
break;
}
}
}
Future对象还允许开发人员在任务尚在运行时取消任务。如下面的代码所示。在任务提交后,可以使用Future.cancel()方法取消任务。如果任务已经完成,或者已被取消,则cancel()方法会执行失败。如果任务尚未开始,则该任务会被取消,永远不会被执行。如果任务正在执行,则该任务会被终止。如果参数是true的话,该任务会被打断(Interrupted)而终止。
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FutureExample {
public static void main(String[] args) {
// 创建一个Callable对象,计算一个整数的平方,
Integer i = 10;
Callable callable = () -> {return i * i;};
// 创建一个单线程的线程池,将Callable对象提交给线程池执行
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(callable);
// 取消任务
future.cancel(true);
}
}
本章节介绍了Java异步运行机制中的两个重要概念Callable和Future。Callable描述的是一项任务,而Future则描述的是如何跟踪和获取一项任务执行的状态和结果。这两个概念非常重要,我们会在后续章节介绍线程池ExecutorService时再次使用Callable和Future的概念。
注册用户登陆后可留言