构建一个新的线程的代价还是有些高的,因为它涉及与操作系统的交互。如果你的程序创建了大量生存期很短的线程,那就应该使用线程池。一个线程池包含大量准备运行的空闲线程。你将一个Runnable对象给线程池,线程池中的一个线程就会调用run方法。当run方法退出时,线程不会死亡,而是继续在池中准备为下一个请求提供服务。
执行器(Executor)类有大量用来构建线程池的静态工厂方法,下表给出了一个总结。
方法 | 描述 |
newCachedThreadPool | 在需要时创建新线程:空闲线程会被保留60秒 |
newFixedThreadPool | 池包含固定数量的线程;空闲线程会一直被保留 |
newSingleThreadExecutor | 只有一个线程的“池”,这个线程顺序执行每一个递交上来的任务 |
newScheduledThreadPool | 为预定执行而构建的固定线程池 |
newSingleThreadScheduledExecutor | 为预定执行而构建的单线程“池” |
newCachedThreadPool、newFixedThreadPool和newSingleThreadExecutor这三个方法返回ThreadPoolExecutor类(这个类实现了ExecutorService接口)对象。
向线程池提交任务的方法为:将一个实现Runnable或Callable接口的对象提交给ExecutorService:
- Future<?> submit(Runable task)
- Future<T> submit(Runable task, T result)
- Future<t> submit(Callable<T> task)
线程池会在适当的时候尽早执行提交的任务,调用submit时会返回一个Future对象,用以查询该任务的状态,或者取消该任务。
第一个submit方法提交一个Runable对象返回一个Future<?>,可使用该对象调用isDone、cancel、或者isCancelled来查询任务状态。但是此Future对象的get方法在任务完成的时候知识简单的返回null;
第二个版本的submit方法同样提交一个Runable对象,并且返回Future的get方法在任务完成的时候返回传入的result对象;
第三个submit方法提交一个Callable对象,并且返回的Future对象将在计算结构、准备好的时候得到它。
当想要注销一个线程池,可调用shutdown方法,该方法启动该线程池的关闭序列。此时线程池并不是马上就壮烈牺牲了线程也没了,而是等待所以任务都完成以后,线程池中的线程才会死亡,被关闭的执行器不再接受新任务。也可以调用shutdownNow,此时线程池会取消正在排队等待处理的任务并且试图中断正在执行的线程。
下面总结了在使用连接池时应该做的事:
除了常规的计算匹配文件数量外,这个程序打印出执行过程中池中的最大线程数量。但从ExecutorService接口不能得到这个信息。因此,我们必须将pool对象转型成一个ThreadPoolExecutor类对象。
- import java.io.*;
- import java.util.*;
- import java.util.concurrent.*;
- public class ThreadPoolTest
- {
- public static void main(String[] args) throws Exception
- {
- 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 and its subdirectories that contain a given keyword.
- */
- class MatchCounter implements Callable<Integer>
- {
- /**
- * 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;
- }
- public Integer call()
- {
- count = 0;
- try
- {
- File[] files = directory.listFiles();
- ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>();
- 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
- {
- Scanner in = new Scanner(new FileInputStream(file));
- boolean found = false;
- while (!found && in.hasNextLine())
- {
- String line = in.nextLine();
- if (line.contains(keyword)) found = true;
- }
- in.close();
- return found;
- }
- catch (IOException e)
- {
- return false;
- }
- }
- private File directory;
- private String keyword;
- private ExecutorService pool;
- private int count;
- }