Java核心技术 卷1-总结-19
Callable与Future
Runnable
封装一个异步运行的任务,可以把它想象成为一个没有参数和返回值的异步方法。Callable
与Runnable
类似,但是有返回值。 Callable接口是一个参数化的类型,只有一个方法call。
public interface Callable<V> {
V call () throws Exception;
}
类型参数是返回值的类型。例如,Callable<Integer>
表示一个最终返回Integer
对象的异步计算。
Future
保存异步计算的结果。可以启动一个计算,将Future
对象交给某个线程,然后忘掉它。Future对象的所有者在结果计算好之后就可以获得它。
Future接口具有下面的方法:
public interface Future<V> {
V get ) throws ...;
V get(long timeout,TimeUnit unit)throws ...;
void cancel(boolean mayInterrupt);
boolean isCancelled();
boolean isDone();
}
第一个 get
方法的调用被阻塞,直到计算完成。如果在计算完成之前,第二个方法的调用超时,抛出一个TimeoutException
异常。如果运行该计算的线程被中断,两个方法都将抛出InterruptedException
。如果计算已经完成,那么 get
方法立即返回。
如果计算还在进行,isDone
方法返回false
;如果完成了,则返回true
。
可以用cancel
方法取消该计算。如果计算还没有开始,它被取消且不再开始。如果计算处于运行之中,那么如果mayInterrupt
参数为true
,它就被中断。
FutureTask
包装器是一种非常便利的机制,可将Callable
转换成Future
和Rumnable
,它同时实现二者的接口。例如:
Callable<Integer> myComputation = ...;
FutureTask<Integer> task = new FutureTask<Integer>(myComputation);
Thread t = new Thread(task);//it's a Runnable
t.start();
...
Integer result = task.get();// it's a Future
计算匹配的文件数目,需要一个长时间运行的任务,它产生一个整数值,一个Callable<Integer>
的例子如下:
class MatchCounter implements Callable<Integer> {
public MatchCounter(File directory, String keyword) { ... }
public Integer call(){....}//returns the number of matching files
然后利用MatchCounter
创建一个FutureTask
对象,并用来启动一个线程。
FutureTask<Integer> task = new FutureTask<Integer>(counter);
Thread t = new Thread(task);
t.start();
最后,打印结果。
System.out.println(task.get() + "matching files.");
注意:对get
的调用会发生阻塞,直到有可获得的结果为止。
在call
方法内部,使用相同的递归机制。对于每一个子目录,我们产生一个新的MatchCounter
并为它启动一个线程。此外,把FutureTask
对象隐藏在ArayList<Future<Integer>>
中。最后,把所有结果加起来:
for (Future<Integer> result : results) {
count += result.get();
}
每一次对get
的调用都会发生阻塞直到结果可获得为止。当然,线程是并行运行的,因此,很可能在大致相同的时刻所有的结果都可获得。
具体程序如下:
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 andits subdirectories that contain a given keyword.
*/
class MatchCounter implements Callable<Integer> {
private File directory;
private String keyword;
/**
* Constructs a MatchCounter.
*
* @param directory the di rectory 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;
}
@Override
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;
}
}
}
java.util.concurrent.Callable5.0
V call()
运行一个将产生结果的任务。
java.util.concurrent.Future5.0
V get()
V get(long time, TimeUnit unit)
获取结果,如果没有结果可用,则阻塞直到真正得到结果超过指定的时间为止。如果不成功,第二个方法会抛出TimeoutException
异常。
boolean cancel(boolean mayInterrupt)
尝试取消这一任务的运行。如果任务已经开始,并且mayInterrupt
参数值为true
,它就会被中断。如果成功执行了取消操作,返回true
。
boolean isCancelled()
如果任务在完成前被取消了,则返回true
。
boolean isDone()
如果任务结束,无论是正常结束、中途取消或发生异常,都返回true
。
java.util.concurrent.FutureTask5.0
FutureTask(Callable<V>task)
FutureTask(Runnable task, V result)
构造一个既是Future<V>
又是Runnable
的对象。
执行器
构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池(thread pool)。 一个线程池中包含许多准备运行的空闲线程。将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
另一个使用线程池的理由是减少并发线程的数目。创建大量线程会大大降低性能甚至使虚拟机崩溃。 如果有一个会创建许多线程的算法,应该使用一个线程数"固定的"线程池以限制并发线程的总数。
执行器(Executor)类有许多静态工厂方法用来构建线程池:
表14-2 执行者工厂方法
方 法 | 描 述 |
---|---|
newCachedThreadPool | 必要时创建新线程;空闲线程会被保留60秒 |
newFixedThreadPool | 该池包含固定数量的线程;空闲线程会一直被保留 |
newSingleThreadExecutor | 只有一个线程的“池”,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程) |
newScheduledThreadPooll | 用于预定执行而构建的固定线程池,替代java.util.Timer |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程“池” |
线程池
newCachedThreadPool
方法构建了一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。newFixedThreadPool
方法构建一个具有固定大小的线程池。如果提交的任务数多于空闲的线程数,那么把得不到服务的任务放置到队列中。当其他任务完成以后再运行它们。newSingleThreadExecutor
是一个退化了的大小为1的线程池:由一个线程执行提交的任务,一个接着一个。这3个方法返回实现了ExecutorService
接口的ThreadPoolExecutor
类的对象。
可用下面的方法之一将一个Runnable
对象或Callable
对象提交给ExecutorService
:
Future<?> submit(Runnable task)
Future<T> submit(Runnable task, T result)
Future<T> submit(Callable<T> task)
线程池会在方便的时候尽早执行提交的任务。调用submit
时,会得到一个Future
对象,可用来查询该任务的状态。
第一个submit
方法返回一个Future<?>
。可以使用这样一个对象来调用isDone
、cancel
或isCancelled
。但是,get
方法在完成的时候只是简单地返回null
。
第二个版本的Submit
也提交一个Runnable
,并且Future
的get
方法在完成的时候返回指定的result
对象。
第三个版本的Submit
提交一个Callable
,并且返回的Future
对象将在计算结果准备好的时候得到它。
当用完一个线程池的时候,调用shutdown
。该方法启动该池的关闭序列。被关闭的执行器不再接受新的任务。当所有任务都完成以后,线程池中的线程死亡。另一种方法是调用shutdownNow
。该池取消尚未开始的所有任务并试图中断正在运行的线程。
下面总结了在使用连接池时应该做的事:
- 调用Executors类中静态的方法
newCachedThreadPool
或newFixedThreadPool
。 - 调用
submit
提交Runnable
或Callable
对象。 - 如果想要取消一个任务,或如果提交
Callable
对象,那就要保存好返回的Future
对象。 - 当不再提交任何任务时,调用
shutdown
。
例如,程序产生了大量的生命期很短的线程,每个目录产生一个线程。下面的程序使用了一个线程池来运行该任务。出于信息方面的考虑,这个程序打印出执行中池中最大的线程数。但是不能通过ExecutorService
这个接口得到这一信息。因此,必须将该pool对象强制转换为ThreadPoolExecutor
类对象。
public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
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();
ExecutorService pool = Executors.newCachedThreadPool();
MatchCounter counter = new MatchCounter(new File(directory), keyword, pool);
Future<Integer> result = pool.submit(counter);
try {
System.out.println(result.get() + "matching files.");
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
}
pool.shutdown();
int largestPoolSize = ((ThreadPoolExecutor) pool).getLargestPoolSize();
System.out.println("largest pool size=" + largestPoolSize);
}
}
}
/**
* This task counts the files in a directory andits subdirectories that contain a given keyword.
*/
class MatchCounter implements Callable<Integer> {
private File directory;
private String keyword;
private ExecutorService pool;
private int count;
/**
* Constructs a MatchCounter.
* @param directory the directory in which to start the search
* @param keyword the keyword to look for
* @param pool the thread pool for submitting subtasks
*/
public MatchCounter(File directory,String keyword,ExecutorService pool) {
this.directory = directory;
this.keyword = keyword;
this.pool = pool;
}
@Override
public Integer call() {
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, pool);
Future<Integer> result = pool.submit(counter);
results.add(result);
} 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;
}
}
}
java.util.concurrent.Executors 5.0
ExecutorService newCachedThreadPool()
返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲60秒之后终止线程。
ExecutorService newFixedThreadPool(int threads)
返回一个线程池,该池中的线程数由参数指定。
ExecutorService newSingleThreadExecutor()
返回一个执行器,它在一个单个的线程中依次执行各个任务。
java.util.concurrent.ExecutorService 5.0
Future<T> submit(Callable<T> task)
Future<T> submit(Runnable task, T result)
Future<?> submit(Runnable task)
提交指定的任务去执行。
void shutdown()
关闭服务,会先完成已经提交的任务而不再接收新的任务。
java.util.concurrent.ThreadPoolExecutor 5.0
int getLargestPoolSize()
返回线程池在该执行器生命周期中的最大尺寸。