一、Callable接口与Future使用示例(引自Java编程思想)
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望在任务完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()(而不是run())中返回的值,并且必须使用ExecutorService.submit()方法调用它,下面是一个简单的示例:
package chapter21; import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; class TaskWithResult implements Callable<String> { private int id; public TaskWithResult(int id) { this.id = id; } public String call() { return "result of TaskWithResult " + id; } } public class CallableDemo { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<Future<String>>(); for (int i = 0; i < 10; i++) //submit()方法会产生Future对象 results.add(exec.submit(new TaskWithResult(i))); for (Future<String> fs : results) try { //知道完成,否则线程会阻塞 System.out.println(fs.isDone()); System.out.println(fs.get()); } catch (InterruptedException e) { System.out.println(e); return; } catch (ExecutionException e) { System.out.println(e); } finally { //关闭线程池 exec.shutdown(); } } }
运行结果:
true
result of TaskWithResult 0
true
result of TaskWithResult 1
true
result of TaskWithResult 2
true
result of TaskWithResult 3
true
result of TaskWithResult 4
true
result of TaskWithResult 5
true
result of TaskWithResult 6
true
result of TaskWithResult 7
true
result of TaskWithResult 8
true
result of TaskWithResult 9
result of TaskWithResult 0
true
result of TaskWithResult 1
true
result of TaskWithResult 2
true
result of TaskWithResult 3
true
result of TaskWithResult 4
true
result of TaskWithResult 5
true
result of TaskWithResult 6
true
result of TaskWithResult 7
true
result of TaskWithResult 8
true
result of TaskWithResult 9
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化。你可以用isDone()方法来查询Future是否已经完成。当任务完成时,它具有一个结果,你可以调用get()方法来获取该结果。你也可以不用isDone()进行检查就直接调用get(),在这种情况下,get()将阻塞,直至结果准备就绪。你还可以在试图调用get()来获取结果之前,先调用具有超时的get(),或者来调用isDone()来查看任务是否完成。
二、Callable与Future详解(参考Java核心技术(卷一))
Runnable封装了一个异步运行的任务,可以把它想象成一个没有参数和返回值的异步方法。Callable与Runnable类似,但是有返回值。Callable接口是一个参数化的类型,只有一个方法call。
public interface Callable<V> {
V call() throws Exception;
}
参数类型是返回值的类型。例如:Callable<Integer>表示一个最终返回Integer对象的异步计算。
Future保存异步计算的结果,可以启动一个计算,将Future对象交给某个线程,然后忘掉他。Future对象的所有者在结果计算好之后可以获得它。
Future接口具有下面的方法:
public interface Future<V> { /** * 用于取消该计算。如果计算还没有开始,它被取消且不再开始。如果计算处于运行中,如果参数为true,它就被中断。 */ boolean cancel(boolean mayInterruptIfRunning); /** * 如果线程完成之前被取消,放回true */ boolean isCancelled(); /** * 如果计算还在进行,该方法返回false;如果完成了,则返回true。 */ boolean isDone(); /** * 该方法的调用被阻塞,知道计算完成,如果运行该计算的线程被中断,该方法抛出InterruptedException。如果计算已经完成,那么get()方法立即返回。 */ V get() throws InterruptedException, ExecutionException; /** * 如果在计算完成之前,该方法调用超时,则抛出TimeoutException异常。其他与上一个方法一样. */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
FutureTask包装器是一种非常便利的机制,可将Callable转换成Future和Runnable,它同时实现二者的接口。
构造器代码如下:用于构造既是Future<V>又是Runnable的对象。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
书上的例子,添加了注释,main方法中做了改动:
package chapter14;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* 计算给定目录下的所有文件中包含指定字符串的文件个数
*/
public class FutureTest {
public static void main(String[] args) {
//Scanner in = new Scanner(System.in);
//System.out.println("Enter base directory (e.g. /usr/local/jdk/src)");
//String directory = in.nextLine();
//System.out.println("Enter keyword (e.g volatile)");
//String keyword = in.nextLine();
String directory = "D:\\test";
String keyword = "jdbc";
MatchCounter counter = new MatchCounter(new File(directory), keyword);
FutureTask<Integer> task = new FutureTask<Integer>(counter);
Thread t = new Thread(task);
t.start();//启动线程,执行call()中的代码
try {
System.out.println(task.get() + " matching files.");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MatchCounter implements Callable<Integer> {
private File directory;//要搜索的目录
private String keyword;//要搜索的关键字
private int count;//数量
public MatchCounter(File directory, String keyword) {
this.directory = directory;
this.keyword = keyword;
}
public Integer call() throws Exception {
count = 0;
File[] files = directory.listFiles();//列出目录下的所有文件和目录
List<Future<Integer>> results = new ArrayList<Future<Integer>>();
//这里用到了递归
for(File file : files) {
if(file.isDirectory()) {
MatchCounter counter = new MatchCounter(file, keyword);
//将Callable包装成既是Future又是Runnable的对象
FutureTask<Integer> task = new FutureTask<Integer>(counter);
results.add(task);
Thread t = new Thread(task);
t.start();
}else {
if(search(file)) {
count ++;
}
}
}
for(Future<Integer> result:results) {
count += result.get();
}
//返回数目
return count;
}
/**
* 在文件中搜索指定的关键字
* @param file
* @return
*/
public boolean search(File file) {
InputStream is = null;
Scanner in = null;
try{
is = new FileInputStream(file);
in = new Scanner(is);
boolean found = false;
while(!found && in.hasNextLine()) {
//读取一行
String line = in.nextLine();
//如果包含关键字,则停止读取
if(line.contains(keyword)) {
found = true;
}
}
return found;
} catch (FileNotFoundException e) {
return false;
} finally{
try {
if(is != null) {
is.close();
}
if(in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
2 matching files.
三、将线程交给CompletionService执行(参考自Java多线程与高并发库应用)
具体代码如下:
package tradition;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 实现Callable接口创建线程
*/
public class CallableAndFuture {
public static void main(String[] args) {
//创建只有单个线程的线程池
ExecutorService threadPool =
Executors.newSingleThreadExecutor();
//使用Callable的好处是可以获取结果
Future<String> future =
threadPool.submit(
new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "hello";
}
}
);
System.out.println("等待结果:");
try {
System.out.println("拿到结果:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//创建一个带有十个线程的线程池
ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
//将线程交给ExecutorCompletionService,哪个任务先完成就先返回值
CompletionService<Integer> completionService =
new ExecutorCompletionService<>(threadPool2);
//提交10个任务
for(int i = 1; i <= 10; i ++) {
final int seq = i;
completionService.submit(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
Thread.sleep(new Random().nextInt(5000));
return seq;
}
});
}
for(int i = 0; i < 10; i ++) {
//获取返回结果
try {
System.out.println(completionService.take().get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}