继续上次读取外部存储器中文件数量的任务,上次采用Callable、Future及FutureTask实现,并没有限制线程的数量,由于创建了大量的生命周期很短暂的线程,造成过度占用系统资源,效率非常低下,在我的Android手机上获取文件数量这一任务大概要执行40s左右。简直不可忍受,这次采用线程池来实现。先学习一下线程池的基本操作。
创建线程池之执行器
构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命周期很短的线程,应该使用线程池(thread pool),一个线程池中包含许多准备运行的空闲线程。将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出时。线程不会死亡,而是在池中准备为下一个请求提供服务。
另一个使用线程池的理由是减少并发线程的数目。创建大量的线程会啊大大降低性能甚至使虚拟机崩溃,如果有一个会创建许多线程的算法,应该使用一个线程数固定的线程池以限制线程的总数。
执行器(Executor)
类中有许多静态工厂方法用来构造线程池
method | intro |
---|---|
newCachedThreadPool | 返回一个带缓存的线程池, 该池在必要的时候创建线程, 在线程空闲 60 秒之后终止线程,返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。 |
newFixedThreadPool | 该池包含固定数量的线程;空闲线程会一直被保留,如果提交的任务数大于空闲的线程数,那么把得不到服务的任务放到队列中。返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。 |
newSingleThreadExecutor | 只有一个线程的池,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程)返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。 |
ScheduledExecutorService newScheduledThreadPool(int threads) | 用于预定执行而构建的固定线程池,返回一个线程池,它使用给定的线程数来调度任务。 |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程池 |
预定执行
ScheduledExecutorService 接口具有为预定执行( Scheduled Execution ) 或重复执行任务而设计的方法。它是一种允许使用线程池机制的 java.util.Timer 的泛化。 Executors 类的newScheduledThreadPool 和 newSingleThreadScheduledExecutor 方法将返回实现了 ScheduledExecutorService 接口的对象。
可以预定 Runnable 或 Callable 在初始的延迟之后只运行一次。也可以预定一个 Runnable对象周期性地运行。
method | intro |
---|---|
ScheduledFuture<V> schedule(Callable<V> task , long time, Timellnit unit) | 预定在指定的时间之后执行任务。 |
ScheduledFuture<?> schedule(Runnable task , long time , TimeUnit unit ) | 预定在指定的时间之后执行任务。 |
ScheduledFuture<?> scheduleAtFixedRate(Runnable task , long initialDelay,long period,TimeUnit unit) | 预定在初始的延迟结束后, 周期性地运行给定的任务, 周期长度是 period |
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task ,long initialDelay , long delay,TimeUnit unit ) | 预定在初始的延迟结束后周期性地运行给定的任务, 在一次调用完成和下一次调用开始之间有长度为 delay 的延迟。 |
提交任务到线程池中:
线程池创建好后会返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。
可用下面方法之一将一个Runnable对象或Callable对象提交给ExecutorService
method | intro |
---|---|
Future<?> submit(Runnable task) | 返回Future<?>,可以使用这样一个对象来调用isDone、cancel或isCancelled。但是get方法在完成的时候只是简单的返回null。 |
Future<V> submit(Runnable task,T result) | 提交一个Runnable,并且Future的get方法在完成的时候返回指定的result对象。 |
Future<V> submit(Callable<V> task) | 提交一个Callable,并且返回的Future对象将在计算结果准备耗材的时候得到它。 |
int getLargestPoolSize() | 返回线程池在该执行器生命周期中最大尺寸 |
关闭线程池
method | intro |
---|---|
void shutdown() | 启动该线程池的关闭序列。被关闭的执行器不再接收新的任务。当所有任务都完成后,线程池中的线程死亡。 |
void shutdownNow() | 取消尚未开始的所有任务并试图中断正在 运行的线程。 |
控制任务组
可以使用执行器控制一组相关任务,例如,可以在执行器中使用shutdownNow方法取消所有的任务。
invokeAny方法提交所有对象到一个Callable对象的集合汇总,并返回 某个已经完成了的任务的结果。无法获得返回的究竟是哪个任务的结果。
invokeAll方法提交所有对象到一个Callable对象的集合中,并返回一个Future对象的列表,代表所有任务的解决方案。当计算结果可获得时,可以像下面这样对结果进行处理。
List<Callable<T>> tasks=...;
List<Future<T>> results = executor.invokeAll(tasks);
for(Future<T> result:results){
processFurther(result.get());
}
这个方法有个缺点,如果第一个任务恰花掉了很长时间,则可能不得不进行等待。这时候 将结果按可获得的顺序保存起来可能更有实际意义。可以用ExecutorCompletionService来进行排列。首先 用常规的方法获得一个执行器。然后构建一个ExecutorCompletionService,提交任务给完成服务(Completion service),该服务管理Future对象的阻塞队列,其中包含以及提交的任务的执行结果(当这些结果成为可用时)。相比前面的计算,一个更有效的组织形式如下:
ExecutorCompletionService<T> service = new ExecutorCompletionService<>(executor);
for(Callable<T> task:tasks)service.submit(task);
for(int i=0;i<tasks.size();i++){
processFurther(service.take().get());
}
method | intro |
---|---|
void shutdown() | 启动该线程池的关闭序列。被关闭的执行器不再接收新的任务。当所有任务都完成后,线程池中的线程死亡。 |
void shutdownNow() | 取消尚未开始的所有任务并试图中断正在 运行的线程。 |
T invokeAny( Collection<Callable<T>> tasks ) | 执行给定的任务, 返回其中一个任务的结果 |
T invokeAny(Collection<Callable<T>> tasks , long timeout , TimeUnit unit ) | 执行给定的任务, 返回其中一个任务的结果。若发生超时, 抛出一个TimeoutException 异常。 |
List<Future<T>> invokeAll (Collection<Callable<T>> tasks ) | 执行给定的任务, 返回所有任务的结果。 |
List <Future<T>> invokeAll (Collection<Callabl e<T>> tasks ,long timeout, TimeUnit unit ) | 执行给定的任务, 返回所有任务的结果。若发生超时,拋出一个 TimeoutException异常。 |
ExecutorCompletionService(Executor e) | 构建一个执行器完成服务来收集给定执行器的结果 |
Future<V> submit( Callable<V> task ) | 提交一个任务给底层的执行器。 |
Future<V> submit( Runnable task , V result ) | 提交一个任务给底层的执行器。 |
Future<V> take() | 移除下一个已完成的结果, 如果没有任何已完成的结果可用则阻塞。 |
Future<V> poll () | 移除下一个已完成的结果, 如果没有任何已完成结果可用则返回 null。 |
代码构思和上次一样,唯一不同的是采用了线程池来管理线程,这样做有个好处,不必频繁的向系统创建新的线程,当任务执行完毕后,线程并不会立即死亡,而是在线程池中等待下一个任务。线程复用可以大大减少并发线程的数目,有利于提高软件性能降低系统负担。
测试线程池代码:
/**
* Author : Makesky
* File : ThreadPoolActivity.java
* Intro : 线程池测试
* Version : 1.0
* Date : 2018/4/26 16:42
*/
public class ThreadPoolActivity extends AppCompatActivity {
private String TAG=getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_pool);
new Thread(new Runnable() {
@Override
public void run() {
File filePath = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory() : getCacheDir();
LogUtil.i(TAG,"文件夹路径:"+filePath);
ExecutorService pool= Executors.newCachedThreadPool();
MatchCounter counter=new MatchCounter(filePath,null,pool);
Future<Integer> result= pool.submit(counter);//提交Callable
try {
int fileCount=result.get();
LogUtil.i(TAG,"文件数量:"+fileCount);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
int LargestPoolSize=((ThreadPoolExecutor) pool).getLargestPoolSize();
LogUtil.i(TAG,"LargestPoolSize:"+LargestPoolSize);
int PoolSize=((ThreadPoolExecutor) pool).getPoolSize();
LogUtil.i(TAG,"PoolSize:"+PoolSize);
int MaximumPoolSize=((ThreadPoolExecutor) pool).getMaximumPoolSize();
LogUtil.i(TAG,"MaximumPoolSize:"+MaximumPoolSize);
int CorePoolSize=((ThreadPoolExecutor) pool).getCorePoolSize();
LogUtil.i(TAG,"CorePoolSize:"+CorePoolSize);
pool.shutdown();
}
}).start();
}
class MatchCounter implements Callable<Integer>{
private File directory;
private String keyword;
private ExecutorService pool;
private int count;
public MatchCounter(File directory, String keyword, ExecutorService pool){
this.directory=directory;
this.keyword=keyword;
this.pool=pool;
}
@Override
public Integer call() throws Exception {
count=0;
File[] files=directory.listFiles();
List<Future<Integer>> list= new ArrayList<>();
for(File file:files){
if(file.isDirectory()){
MatchCounter counter=new MatchCounter(file,null,pool);
Future<Integer> res=pool.submit(counter);
list.add(res);
}else {
count++;
}
}
for(Future<Integer> result:list){
try{
count+=result.get();
}catch (ExecutionException e){
e.printStackTrace();
}
}
return count;
}
}
}
可以看出使用了线程池后,执行时间为8秒左右,线程池中最大的线程数量为1581。执行效率大大提高了。
Log信息:
cpu占用率: