JDK5中的Callable与Future的应用:获取一个线程的运行结果

一、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

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();
			}
		}
	}
	
}













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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值