Java核心技术 卷1 多线程----执行器(5)
构建一个新的线程是有一定代码的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池(thread pool)。一个线程池中包含许多准备运行的空闲线程。将Runnable是交给线程池,就会有一个线程调用
run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。 另一个使用线程池的理由是减少并发线程的数目。创建大量线程会大大降低性能甚至使虚拟机崩溃。如果有一个会创建许多线程的算法,应该使用一个线程数“固定的”线程池以限制并发线程的总数。
执行器(Executor)类有许多静态工厂方法用来构建线程池。如下:
方 法 描 述
newCachedThreadPool 必要时创建新线程:空闲线程会被保留60秒
newFixedThreadPool 该池包含固定数量的线程;空闲线程会一直保留
newSingleThreadExecutor 只有一个线程的“池”,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程)
newScheduledThreadPool 用于预定执行而构建的固定线程池,替代java.util.Timer
newSingleThreadScheduledExecutor 用于预定执行而构建的单线程“池”
线程池
newCachedThreadPool方法构建一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。newFixedThreadPool方法构建一个具有固定大小的线程池。如果提交的任务数多于空闲的线程数,那么把得不到服务的。任务旋转到队列中。当其他任务完成以后再运行它们。newSingleThreadExecutor是一个退化了的大小为1的线程池:由一个线程执行提交的任务,一个接一个。这3个方法返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。
可用下面的方法之一将一个Runnable对象或Callable对象提交给ExecutorService:
java.util.concurrent.ExecutorService
Future<?> submit(Runnable task) 返回Future<?> 可以调用isDone、cancel或isCancelled。get方法在完成的时候只是简单的返回null
Future<T> submit(Runnable task,T result) 提交一个Runnable对象,并且Futrue的get方法在完成的时候返回指定的result对象。
Future<T> submit(Callable<T> task) 提交一个Callable,并且返回的Futrue对象将在计算结果准备好的时候的得到它。
该线程池会在方便的时候尽早执行提交的任务。调用submit时,会得到一个Future对象,可用来查询该任务的状态。当用完一个线程池的时候,调用shutdown。该方法启动该池的关闭序列。被关闭的执行器不再接受新的任务。当所有的任务都完成以后,线程池中的线程死亡。另一种方法是调用shutdownNow。该池取消尚未开始的所有任务并试图中断正在运行的线程。
总结使用连接池时应该做的事:
1)调用Executors类中静态的方法newCachedThreadPool或newFixedThreadPool。
2)调用submit提交Runnable或Callable对象。
3)如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。
4)当不再提交任何任务时,调用shutdown。
例如,前几篇笔记里的程序例子产生了大量的生命期很短的线程,每个目录产生一个线程。 以下代码将演示使用一个线程池来运行任务。(出于信息方面的考虑,这个程序打印出执行中池中最大的线程数。但是不能通过ExecutorService这个接口得到这一信息。因此必须将该pool对象强制转换为ThreadPoolExecutor类对象)。
package test.ThreadPool; import java.io.File; import java.util.Scanner; import java.util.concurrent.*; /** * Created by Administrator on 2017/11/27. */ public class ThreadPoolTest { public static void main(String[] args) { Scanner in=new Scanner(System.in); System.out.print("输入目前路径:"); String directory=in.nextLine(); System.out.print("输入关键字:"); String keyword=in.nextLine();
//1.调用Executors类中静态的方法newCachedThreadPool ExecutorService pool= Executors.newCachedThreadPool(); MatchCounterThreadPool counter=new MatchCounterThreadPool(new File(directory),keyword,pool);
//2.调用submit提交Runnable或Callable对象。
//3.如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。 Future<Integer> result=pool.submit(counter); try{ System.out.println(result.get()+" 匹配文件"); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
//4.当不再提交任何任务时,调用shutdown。 pool.shutdown();
//返回线程池在该执行器生命周期中的最大尺寸 int largestPoolSize=((ThreadPoolExecutor)pool).getLargestPoolSize(); System.out.println("线程池大小="+largestPoolSize); } }
package test.ThreadPool; import java.io.File; import java.io.IOException; 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.ExecutorService; import java.util.concurrent.Future; /** * 此任务对包含给定关键字的目录及其子目录中的文件进行计数 */ public class MatchCounterThreadPool implements Callable<Integer> { private File directory; private String keyword; private ExecutorService pool; private int count; /** * * @param directory 开始查询的文件 * @param keyword 寻找关键字 * @param pool 用于提交子任务的线程池 */ public MatchCounterThreadPool(File directory, String keyword, ExecutorService pool) { this.directory=directory; this.keyword=keyword; this.pool=pool; } @Override public Integer call() throws Exception { count=0; try{ File[] files=directory.listFiles(); List<Future<Integer>> results=new ArrayList<>(); for (File file:files ) { if (file.isDirectory()){ MatchCounterThreadPool counter=new MatchCounterThreadPool(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; } /** * 搜索一个给定关键字的文件 * * @param file * @return */ public boolean search(File file) { try { try (Scanner in = new Scanner(file, "gbk")) { boolean isFound = false; while (!isFound && in.hasNextLine()) { String line = in.nextLine(); if (line.contains(keyword)) { isFound = true; } } return isFound; } } catch (IOException e) { return false; } } }
java.util.concurrent.Executors
- ExecutorService newCachedThreadPool()
返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲的60秒后终止线程。
- ExcuorService newFixedThreadPool(int threads)
返回一个线程池,该池中的线程数由参数指定。
- ExecutorService newSingleThreadExecutor()
返回一个执行器,它在一个单个的线程中依次执行各个任务。
java.util.concurrent.ThreadPoolExecutor
- int getLargestPoolSize() 返回线程池在该执行器生命周期中的最大尺寸。
预定执行
ScheduledExecutorService接口具有为预定执行(Scheduled Execution)或重复执行任务而设计的方法。它是一种允许使用线程池机制的java.util.Timer的泛化。Executors类的newScheduledThreadPool和newSingleThreadScheduledExecutor方法将返回实现了ScheduledExecutorService接口对象。
可以预定Runnable或Callable在初始的延迟之后只运行一次。也可以预定一个Runnable对象周期性地运行。
java.util.concurrent.Executors
- ScheduledExecutorService newSheduledThreadPool(int threads) 返回一个线程池,它使用给定的线程数来调度任务。
- ScheduledExecutorService newSingleThreadScheduledExecutor() 返回一个执行器,它在一个单独的线程中调度任务。
java.util.concurrent.ScheduledExecutorService
- ScheduledFuture<V> schedule(Callable<V> task,long time,TimeUnit unit)
- ScheduledFuture<?> schedule(Runnable<?> task,long time,TimeUnit unit) 预订在指定的时间之后执行任务。
- ScheduledFuture<V> scheduleAtFixedRate(Runnable task,long initialDelay,long period,TimeUnit unit) 预定在初始的延迟结束后,周期性地运行给定的任务,周期长度是period。
- ScheduledFuture<V> scheduleWithFixedDelay(Runnable task,long initialDelay,long period,TimeUnit unit) 预定在初始的延迟结束后周期性地给定的任务,在一次调用完成和下一次调用开始之间有长度为delay的延迟。
Fork-Join框架
一个Web服务器可能会为每个连接分别使用一个线程。另外一些应用可能对每个处理器内核分别使用一个线程,来完成计算密集型任务,如图像或视频处理。java SE 7中引入了fork-join框架,专门用来支持后一类应用。
在后台,fork-join框架使用了一种有效的智能方法来平衡可用线程的工作负载,这种方法称为工作密取(work stealing)。每个工作线程都有一个双端队列(deque)来完成任务。一个工作线程将子任务压入其双端队列的队头。(只有一个线程可以访问队头,所以不需要加锁)一个工作线程空闲时,它会从另一个双端队列的队尾"密取”一个任务。由于大的子任务都在队尾,这种密取很少出现。
package test.ForkJoin; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.RecursiveTask; public class CountTask extends RecursiveTask<Integer> { public static final int threshold=2; private int start; private int end; public CountTask(int start,int end){ this.start=start; this.end=end; } @Override protected Integer compute() { int sum=0; //如果任务足够小就计算任务 boolean canCompute=(end-start)<=threshold; if (canCompute){ for (int i = start; i <=end ; i++) { sum+=i; } } else { //如果任务大于阈值,就分裂成两个子任务计算 int middle=(start+end)/2; CountTask leftTask=new CountTask(start,middle); CountTask rightTask=new CountTask(middle+1,end); //执行子任务 leftTask.fork(); rightTask.fork(); //等待任务执行结束合并其结果 int leftResult=leftTask.join(); int rightResult=rightTask.join(); //合并子任务 sum=leftResult+rightResult; } return sum; } public static void main(String[] args) throws ExecutionException, InterruptedException { ForkJoinPool forkJoinPool=new ForkJoinPool(); //生成一个计算任务,计算1+2+3+4 CountTask task=new CountTask(1,100); //执行一个任务 Future<Integer> result=forkJoinPool.submit(task); System.out.println(result.get()); } }