Java 线程池

继续上次读取外部存储器中文件数量的任务,上次采用Callable、Future及FutureTask实现,并没有限制线程的数量,由于创建了大量的生命周期很短暂的线程,造成过度占用系统资源,效率非常低下,在我的Android手机上获取文件数量这一任务大概要执行40s左右。简直不可忍受,这次采用线程池来实现。先学习一下线程池的基本操作。

创建线程池之执行器

构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命周期很短的线程,应该使用线程池(thread pool),一个线程池中包含许多准备运行的空闲线程。将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出时。线程不会死亡,而是在池中准备为下一个请求提供服务。
另一个使用线程池的理由是减少并发线程的数目。创建大量的线程会啊大大降低性能甚至使虚拟机崩溃,如果有一个会创建许多线程的算法,应该使用一个线程数固定的线程池以限制线程的总数。



执行器(Executor)
类中有许多静态工厂方法用来构造线程池

methodintro
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对象周期性地运行。

methodintro
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

methodintro
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()返回线程池在该执行器生命周期中最大尺寸


关闭线程池

methodintro
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());
}
methodintro
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占用率:
这里写图片描述


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值