一般来说,我们创建线程主要有两种方式:①直接继承Thread;②实现Runnable接口。但是这两种方式没有参数和返回值,也就是说在执行完任务后不会返回执行结果。如果想要获取执行结果,则需要通过共享变量或者线程通信,这势必非常麻烦。从Java 1.5开始,提供了Callable和Future接口,通过这两者可以轻松实现执行完成任务后得到任务结果(返回值)。
首先来看一下java.lang.Runnable,该接口里只声明了一个run()方法,该方法返回值为void类型,在执行完任务后不会有返回值:
public interface Runnable { public abstract void run(); }
Callable接口与Runnable类似,但是有返回值。Callable接口位于java.util.concurrent包下,该接口只有一个方法call():
public interface Callable<V> { V call() throws Exception; }
类型参数是返回值的类型。例如Callable<Integer>表示一个最终返回Integer对象的一步计算。一般情况下Callable是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个重载的submit方法:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
Future接口是对具体的Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。该接口位于java.util.concurrent包下,该接口有五个方法(这五种方法能完成三种功能:①判断任务是否完成;②能够中断任务;③获取任务执行结果):
public interface Future<V> { void cancel(boolean mayInterrupt); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }cancel方法:用来取消任务,如果任务还没有开始,它被取消且不再开始,如果任务处于运行中,那么mayInterrupt参数为true,任务就被中断。取消任务成功则返回true,取消任务失败则返回false。如果任务已经完成,则无论mayInterrupt为true还是false,此方法肯定返回false;如果任务正在执行,若mayInterrupt设置为true,则返回true,若mayInterrupt设置为false,则返回false;如果任务还没有执行,则无论mayInterrupt为true还是false,肯定返回true。
isCancelled方法:表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法:表示任务是否已经完成,若任务完成,则返回true;
get()方法:用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)方法:用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
由于Future只是一个接口,无法直接用来创建对象使用,好在有FutureTask包装器,可将Callable转换成Future和Runnable,它同时实现了二者的接口,因此FutureTask既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。FutureTask提供了2个构造器:
public FutureTask(Callable<V> callable) { } public FutureTask(Runnable runnable, V result) { }
下面举例如何使用Callable和Future,下面的例子源自Java核心技术卷I——并发,该例子主要功能是根据输入的文件路径和关键字,搜索输入的指定路径下所有文件及子路径下文件中是否包含指定的关键字,并统计匹配的文件数目。
import java.io.*; import java.util.*; import java.util.concurrent.*; public class FutureTest { public static void main(String[] args) { try (Scanner in = new Scanner(System.in)) { System.out.print("Enter base directory (e.g. /usr/local/jdk5.0/src): "); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile): "); String keyword = in.nextLine(); MatchCounter counter = new MatchCounter(new File(directory), keyword); FutureTask<Integer> task = new FutureTask<>(counter); Thread t = new Thread(task); t.start(); try { System.out.println(task.get() + " matching files."); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { } } } } /** * This task counts the files in a directory and its subdirectories that contain a given keyword. */ class MatchCounter implements Callable<Integer> { private File directory; private String keyword; /** * Constructs a MatchCounter. * @param directory the directory in which to start the search * @param keyword the keyword to look for */ public MatchCounter(File directory, String keyword) { this.directory = directory; this.keyword = keyword; } public Integer call() { int count = 0; try { File[] files = directory.listFiles(); List<Future<Integer>> results = new ArrayList<>(); for (File file : files) if (file.isDirectory()) { MatchCounter counter = new MatchCounter(file, keyword); FutureTask<Integer> task = new FutureTask<>(counter); results.add(task); Thread t = new Thread(task); t.start(); } else { if (search(file)) count++; } for (Future<Integer> result : results) try { count += result.get(); } catch (ExecutionException e) { e.printStackTrace(); } } catch (InterruptedException e) { } return count; } /** * Searches a file for a given keyword. * @param file the file to search * @return true if the keyword is contained in the file */ public boolean search(File file) { try { try (Scanner in = new Scanner(file, "UTF-8")) { boolean found = false; while (!found && in.hasNextLine()) { String line = in.nextLine(); if (line.contains(keyword)) found = true; } return found; } } catch (IOException e) { return false; } } }