ThreadPoolExecutor使用及Android中常见的4种线程池

线程池功能介绍

通俗的讲就是存放和管理线程的一个池子,那这个池子出现有什么好处呢?在讲之前我们先来了解一些定义:

进程:是系统进行资源分配和调度的一个独立单位,它包括独立的地址空间、系统资源和1-n个线程,进程间切换开销大并且不能共享资源,进程间互不影响
线程:是CPU调度和分派的基本单位,必须依附于进程而存在,线程间切换开销小但共享所在进程的全部资源,一个线程挂掉会导致所在进程挂掉
并发:是多个任务交替使用CPU,同一时刻还是只有一个任务在运行,也就是各个代码块交替执行
并行:是多个任务同时在运行互不影响,也就是各个代码块同时执行

线程的运行分为五个阶段:创建、就绪、运行、阻塞、终止
简单的说就是手机上装的QQ,微信、迅雷等每个应该程序都至少有一个进程,而像迅雷里可同时下载多部电影这就是线程干的活了。所以也就得出结论了一个程序至少有一个进程,一个进程至少有一个线程

创建线程的两张方式:

  1. 继承Thread类,重写其run方法
  2. 实现Runnable接口,重写其run方法

由于java只允许单继承,所以如果某个类需要继承其他类,就比较麻烦,实现Runnable接口不存在这样的问题,并且用Runnable接口创建的线程可以共同处理同一份资源而用Thread创建的线程都是各自处理自己的资源。既然实现Runnable接口创建的线程也还不错,那为什么要使用线程池呢?那下面就来对比下:

new Thread()的缺点

  1. 每次创建线程、销毁线程耗费性能
  2. 创建的线程缺乏管理,大量创建线程后相互竞争,会导致过多占用系统资源甚至内存溢出,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪
  3. 不利于扩展,比如定时执行、周期性执行,并发数控制、取消执行等

线程池的优点

  1. 可重用创建好的线程,避免频繁创建、销毁线程带来的性能开销
  2. 可有效控制最多线程并发数,提高系统资源的使用率,避免大量线程之间因互相抢占资源而导致阻塞现象
  3. 能对线程进行管理,并提供定时执行、间隔循环执行,并发数控制等功能

ThreadPoolExecutor介绍

使用线程池就肯定会用到ThreadPoolExecutor类,下面就通过它的构造来说明一下

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory) 
  • corePoolSize:线程池的核心线程数。默认情况下,在创建线程池之后,线程池中的数量是0,除非调用了prestartAllCoreThreads()或prestartCoreThread()方法去预创建所有或者一个核心线程。当有任务时,就会创建一个线程去执行,当创建的线程达到核心线程数量后就会把任务放到缓存队列中
  • maximumPoolSize:线程池的最大线程数,当线程数达到这个值后,默认策略模式会抛出RejectedExecutionException拒绝执行异常
  • keepAliveTime:非核心线程闲置的超时时长,超过这个时间非核心线程就会被回收,当调用了ThreadPoolExector的allowCoreThreadTimeOut(true)方法后,核心线程也就有了超时机制,超时也会被回收,只到线程池中的数量为0
  • unit:keepAliveTime的时间单位,配合使用,有纳秒、微妙、毫秒等
  • workQueue:任务队列,用来存储等待执行的任务,可设置不同的排队策略
  • threadFactory:线程工厂,为线程池提供创建线程的功能
  • handler:拒绝策略。1、在线程池已经关闭 2、任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务,在上面两种情况下,只要满足其中一种时,再次提交新任务时将会被拒绝,默认策略会抛出RejectedExecutionException拒绝执行异常
  • 非核心线程:由最大线程数减去核心线程数,这些线程都受超时机制限制,超时将被回收

下面简单的对workQueue的排队策略和handler拒绝策略做个介绍:

workQueue的几种排队策略:

  1. LinkedBlockingQueue:线程安全的阻塞队列,是对BlockingQueue的一个链表实现,采用先进先出原则,可指定容量,默认最大Integer.MAX_VALUE,对取和添加操作采用不同的锁,提高了并发效率,使用频率较高,因为多线程并行执行时不需要额外的同步
  2. ArrayBlockingQueue:线程安全的阻塞队列,是对BlockingQueue的一个数组实现,采用先进先出原则,需指定大小,取和添加操作使用一把锁,造成争抢,使用相对效率低下。使用频率较低
  3. DelayedWorkQueue:当指定的延迟时间到了,才从队列中获取到该元素去执行,如果有重复的任务,那在执行结束前会重置执行时间并将自己重新加入到DelayedWorkQueue中
  4. SynchronousQueue:线程安全的阻塞队列,也是一个没有容量的并发队列,也就是说每一个插入操作都必须等待一个移除操作完成,才能进行下一个插入操作,当然插入和移除所在的线程可相同,也可不同。见源码:
        public boolean isEmpty() {
                return true;
            }

        public int size() {
                return 0;
            }

handler拒绝策略:

  1. AbortPolicy:直接拒绝并抛出RejectedExecutionException
  2. DiscardPolicy:拒绝并删除任务但不抛出异常
  3. DiscardOldestPolicy:如果执行程序尚未关闭,则将被删除任务队列头部的任务,然后重试执行程序(如果再次失败,则重复此过程)
  4. CallerRunsPolicy:直接使用调用该execute的线程本身来执行

当一个新任务提交到线程池后,线程池处理任务遵循的规则是:

  1. 如果当前线程数还没有达到核心线程数时,线程池会创建一个新的线程来运行此新任务,无论之前创建的线程是否空闲
  2. 如何当前线程池运行的线程数大于核心线程数,且任务队列未满,则将新任务加入到任务队列,直到某一线程空闲,线程池从任务队列中取出新的任务去执行
  3. 如何核心线程都在运行,任务队列已满,但还没超过最大线程数,则会开启一个非核心线程来执行此新任务
  4. 如果线程已达到最大线程数并且都在执行,则默认会拒绝执行这个任务并抛出RejectedExecutionException异常

注意:线程池回收线程时,对所谓的核心线程和非核心线程是一视同仁,直到线程池中的线程数量等于核心线程数量时,回收过程才会停止。

ThreadPoolExecutor使用

private void btn1Click() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 1, TimeUnit.SECONDS,
                new LinkedBlockingDeque(2));
        for (int i = 0; i < 4; i++) {
            final int temp = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(1000);
                    Log.d("run:", "" + temp);
                }
            };
            executor.execute(runnable);
        }
    }

当核心线程数2,任务队列2,最大线程数6时,任务队列为链表结构时,当循环次数i<4时,线程的执行应该是先创建两个核心线程,执行任务,剩下的放到任务队列,待核心线程空闲时再执行任务队列的任务,所以任务0、1永远在任务2、3之前运行。打印结果如下:

10-11 14:41:41.483 24546-24604/com.cn.liuyz.threadpooldemo D/run:: 0
10-11 14:41:41.484 24546-24605/com.cn.liuyz.threadpooldemo D/run:: 1
10-11 14:41:42.483 24546-24604/com.cn.liuyz.threadpooldemo D/run:: 2
10-11 14:41:42.484 24546-24605/com.cn.liuyz.threadpooldemo D/run:: 3

当把循环次数改成i<5时,则任务0、1、4永远先执行,但顺序不确定,然后再会取任务队列中拿任务2、3执行。打印结果如下:

10-10 17:51:36.074 16978-18462/com.cn.liuyz.threadpooldemo D/run:: 1
10-10 17:51:36.077 16978-18461/com.cn.liuyz.threadpooldemo D/run:: 0
10-10 17:51:36.078 16978-18463/com.cn.liuyz.threadpooldemo D/run:: 4
10-10 17:51:37.074 16978-18462/com.cn.liuyz.threadpooldemo D/run:: 2
10-10 17:51:37.078 16978-18461/com.cn.liuyz.threadpooldemo D/run:: 3

通过打印数据也印证了这一点

Android中的四类线程池

1、SingleThreadExecutor(单线程的线程池)
源码如下:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

从源码看:SingleThreadPool核心线程数时1,最大线程数也是1,LinkedBlockingQueue容量是Integer.MAX_VALUE,采用先进先出原则,所以这是一个单线程化的线程池,线程不会被回收,未处理的任务会放到任务队列中,保证了所有任务都在同一线程中顺序执行,因此不需要处理线程同步的问题。
使用:

private void btn2Click() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(1000);
                    Log.d("run:", "" + temp);
                }
            };
            executorService.execute(runnable);
        }
    }

输出结果为

10-10 17:59:33.235 24480-25562/com.cn.liuyz.threadpooldemo D/run:: 0
         10-10 17:59:34.236 24480-25562/com.cn.liuyz.threadpooldemo D/run:: 1
         10-10 17:59:35.237 24480-25562/com.cn.liuyz.threadpooldemo D/run:: 2
         10-10 17:59:36.238 24480-25562/com.cn.liuyz.threadpooldemo D/run:: 3
         10-10 17:59:37.238 24480-25562/com.cn.liuyz.threadpooldemo D/run:: 4

2、FixedThreadPool(定长线程池)
源码如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

从源码上看:FixedThreadPool的核心线程数和最大线程数相等,LinkedBlockingQueue容量也是Integer.MAX_VALUE,采用先进先出原则,所以所有线程也不会被回收,未处理的任务会放到任务队列中,所以FixedThreadPool是一个可创建定长的线程池,可控制最大并发数,当定长为1时和newSingleThreadExecutor一样。
使用:

private void btn3Click() {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(1000);
                    Log.d("run:", "" + temp);
                }
            };
            executorService.execute(runnable);
        }
    }

打印结果如下:

10-10 18:00:24.140 24480-26266/com.cn.liuyz.threadpooldemo D/run:: 2
10-10 18:00:24.143 24480-26265/com.cn.liuyz.threadpooldemo D/run:: 1
10-10 18:00:24.144 24480-26264/com.cn.liuyz.threadpooldemo D/run:: 0
10-10 18:00:25.141 24480-26266/com.cn.liuyz.threadpooldemo D/run:: 3
10-10 18:00:25.143 24480-26265/com.cn.liuyz.threadpooldemo D/run:: 4

3、CachedThreadPool(缓存线程池可全部回收)
源码如下:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

从源码看CachedThreadPool无核心线程,最大线程数为Integer.MAX_VALUE,保活时间为60S,当有新任务是,如果没有空闲线程则会创建新线程,因为任务队列为SynchronousQueue,所以最多有一个任务在队列中等到,也就是说CachedThreadPool可处理的最大任务数为Integer.MAX_VALUE+1。
使用:

private void btn4Click() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(1000);
                    Log.d("run:", "" + temp);
                }
            };
            executorService.execute(runnable);
        }
    }

打印结果如下:

10-11 15:11:20.470 18498-18713/com.cn.liuyz.threadpooldemo D/run:: 2
10-11 15:11:20.472 18498-18712/com.cn.liuyz.threadpooldemo D/run:: 1
10-11 15:11:20.473 18498-18711/com.cn.liuyz.threadpooldemo D/run:: 0
10-11 15:11:20.474 18498-18714/com.cn.liuyz.threadpooldemo D/run:: 3
10-11 15:11:20.474 18498-18715/com.cn.liuyz.threadpooldemo D/run:: 4

4、ScheduledThreadPool(延迟或定时周期性执行任务)
源码如下:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

从源码看ScheduledThreadPool是一个定长的核心线程,Integer.MAX_VALUE最大线程数的线程池,主要用作延迟执行或定时周期性执行任务。
使用:

private void btn5Click(){
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
        //延迟1s执行任务
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                Log.d("run:", "schedule-Runnable");
            }
        },1,TimeUnit.SECONDS);

        //延迟2s执行任务
        ScheduledFuture schedule = executorService.schedule(new Callable() {
            @Override
            public String call() throws Exception {
                return "schedule-Callable";
            }
        }, 2, TimeUnit.SECONDS);
        try {
            String s = schedule.get();
            Log.d("run:", s);
        } catch (Exception e) {
            Log.d("run:", "Exception");
        }

        //表示延迟3秒后每4秒执行一次。
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Log.d("run:" ,"scheduleAtFixedRate");
            }
        },3, 4, TimeUnit.SECONDS);

        //表示延迟3秒后每5秒执行一次
        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                Log.d("run:" ,"scheduleWithFixedDelay");
            }
        }, 3, 5, TimeUnit.SECONDS);
    }

打印结果如下:

10-11 15:16:52.542 18498-23534/com.cn.liuyz.threadpooldemo D/run:: schedule-Runnable
10-11 15:16:53.543 18498-18498/com.cn.liuyz.threadpooldemo D/run:: schedule-Callable
10-11 15:16:56.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleAtFixedRate
10-11 15:16:56.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleWithFixedDelay
10-11 15:16:59.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleAtFixedRate
10-11 15:17:01.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleWithFixedDelay
10-11 15:17:02.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleAtFixedRate
10-11 15:17:05.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleAtFixedRate
10-11 15:17:06.545 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleWithFixedDelay
10-11 15:17:08.544 18498-23533/com.cn.liuyz.threadpooldemo D/run:: scheduleAtFixedRate

Runnable和Callable区别,scheduleAtFixedRate和scheduleWithFixedDelay区别另一篇文章再做详解

至此线程池的内容就介绍完毕了

Dmeo下载

你可能感兴趣的:(普通)