• Android线程池
  • 为何使用线程池
  • ThreadPoolExecutor
  • 参数详解
  • 参数设置
  • 线程池示例
  • Android中线程池的分类
  • FixedThreadPool
  • CachedThreadPool
  • ScheduledThreadPool
  • SingleThreadExecutor


Android线程池

为何使用线程池

我们可以想想这样一个场景,我们要开一家餐厅,每个顾客来吃饭我们都需要给他一个盘子,这样就会产生一个问题,如果有100个人吃饭,那么我就需要给出100个盘子,但是现实中是这样的,我们准备30个盘子,刚来的人每人拿走一个盘子使用,如果人比较多则排队等候,当前边有人使用完走后就会让排队的队列第一个人使用空闲出来的那个盘子。这样我们有30个盘子就可以解决100个人吃饭的问题。

线程池的原理与之类似,能够重用线程池中的线程(餐厅中的盘子),避免因为线程大量的创建和销毁所带来的性能开销。
同时能够有效控制线程池中的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。

Android中线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。

Android中的线程池主要分为四类,而这四类都是通过对ThreadPoolExecutor进行配置来实现的。

ThreadPoolExecutor

参数详解

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

上边代码为ThreadPoolExecutor的构造方法的参数,我们对每一个参数进行分析:

corePoolSize
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使他们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut设置为true,那么核心线程就会像非核心线程一样在等待新任务到来时会有超时策略,即在等待了keepAliveTime之后,核心线程也会被终止。

maximumPoolSize
线程池所能容纳的最大线程数。

keepAliveTime
线程池中的非核心线程等待新的任务时的等待超时时长,当超过该时长时,空闲的非核心线程会被终结。

unit
keepAliveTime的时间单位,TimeUnit是一个枚举值,包括分钟,秒,毫秒等。

workQueue
线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。

threadFactory
线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,只有一个Thread newThread(Runnable r)的方法。

handler
当线程池无法执行新任务时,这可能是由于任务队列已满或者是无法成功执行任务,这个时候会调用handler的rejectedExecution方法。

ThreadPoolExecutor的执行遵循以下规则:
1.如果线程池中的线程数量小于corePoolSize,即使线程池中的线程处于空闲状态,也会创建一个新的线程处理被添加的任务。
2.如果线程池中的线程数量等于corePoolSize,但是workQueue未满,那么就将新任务放入workQueue,即缓冲队列中,当线程有空闲的时候就会从中取出任务进行执行。
3.如果线程池中的线程数量大于corePoolSize,workQueue满了,线程池中的线程数量小于maximumPoolSize,那么就会启动新的线程来执行任务。
3.如果线程池中的线程数量大于corePoolSize,workQueue满了,线程池中的线程数量等于maximumPoolSize,那么就会按照handler中指定的策略来处理此任务。

即处理任务的优先级为:核心线程—-任务队列—-最大线程—-都满了,则调用handler处理

当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

参数设置

那么各个参数设置多少合适呢?我们可以参照AsyncTask,核心线程数=CPU核心数+1;最大线程数=2*CPU核心数+1;超时时间=1s;workQueue的长度为128;

获取CPU核心数:

private final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

线程池示例

public class DefaultThreadPool {
    private final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//CPU核心数
    private int CORE_POOL_SIZE = CPU_COUNT + 1 ; //线程池核心线程数
    private int MAX_POOL_SIZE = 2 * CPU_COUNT + 1 ; //非核心线程数
    private int KEEP_ALIVE_TIME = 1;  //非核心线程闲置时的超时时长
    //线程池的任务队列,通过线程池的execute方法提交的Runnable方法会保存在该队列中
    private static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(128);
    //线程工厂,为线程池提供创建新线程的功能。任务队列中的runnable会通过线程工厂创建为线程。
    private ThreadFactory threadFactory = new ThreadFactory() {
        private  final AtomicInteger integer = new AtomicInteger();
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r,"myThreadPool Thread:"+integer.getAndIncrement());
        }
    };
    //线程池对拒绝任务的处理策略,被拒绝任务会调用该方法。
    private RejectedExecutionHandler handler = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        }
    };
    //初始化线程池对象
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,
                KEEP_ALIVE_TIME, TimeUnit.SECONDS,workQueue,threadFactory,handler);
    //构造方法改为private
    private DefaultThreadPool(){

    }
    //采用单例模式,保证该对象只会被创建一次
    private static DefaultThreadPool instance = null;
    public static DefaultThreadPool getInstance(){
        if(instance == null){
            instance = new DefaultThreadPool();
        }
        return instance;
    }

    /**
     * 添加任务
     * @param runnable
     */
    public void execute(Runnable runnable){
        threadPoolExecutor.execute(runnable);
    }
    /**
     * 关闭并等待任务执行完成,不接受新任务
     */
    public void shutDown(){
        if(threadPoolExecutor!=null){
            threadPoolExecutor.shutdown();
            threadPoolExecutor = null;
        }
        instance = null;
    }

    /**
     * 关闭,立即关闭,并挂起所有正在执行的线程,不接受新任务
     */
    public void shutDownNow(){
        if(threadPoolExecutor != null){
            threadPoolExecutor.shutdownNow();
            try {
                threadPoolExecutor.awaitTermination(1, TimeUnit.MICROSECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor = null;
        }
        instance = null;
    }
}

如上代码,我们自己定义了一个线程池使用的工具类,然后就可以在代码中进行使用:

DefaultThreadPool.getInstance().execute(new Runnable() {
        @Override
        public void run() {
            try {
                //模拟耗时操作
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("currentThreadName:" + Thread.currentThread().getName());
        }
    });

通过这种方式就可以将耗时操作放入线程池中执行。

Android中线程池的分类

以下四类线程池只是在对线程池拒绝的任务处理策略不同而已,如我们上边的代码就没有对拒绝的任务进行处理,那么拒绝的任务就会被直接丢弃。

FixedThreadPool

创建方法如下所示:

ExecutorService myFixedThreadPool = Executors.newFixedThreadPool(20);
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

由以上源码可知,它是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被回收,因为所有线程均为核心线程。当所有线程处于活动状态时,新任务会处于等待状态,知道有线程空闲下来。这些核心线程没有超时机制,且任务队列也是没有大小限制的。

CachedThreadPool

创建方法如下所示:

ExecutorService myCachedThreadPool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

由以上源码可知,它是一种线程数量不定的线程池,所有线程均为非核心线程,并且最大线程数为一个极大的数,因此如果线程池中的线程都处于活动状态时,线程池会创建新的线程进行执行,否则就会利用空闲线程来处理新的任务。线程池中的空闲线程会有超时机制,超时时间为60s,超过该时间的闲置线程就会被回收。SynchronousQueue可以理解为一个无法存储元素的队列,相当于一个空集合,因此任何任务都会被立即执行。

CachedThreadPool适合执行大量的耗时较少的任务,当整个线程池都处于空闲状态时,所有线程都会因为超时而被终止,这个时候CachedThreadPool之中实际上是没有任何线程的,几乎不占用任何系统资源。

ScheduledThreadPool

创建方法如下所示:

ExecutorService myScheduledThreadPool = Executors.newScheduledThreadPool(10);
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());
    }

它是核心线程数量是固定的,非核心线程数没有限制,主要用于执行定时任务和具有周期的重复任务。

SingleThreadExecutor

创建方法如下所示:

ExecutorService mySingleThreadExecutor = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

该线程池内部只有一个核心线程,它确保所有任务都在同一个线程中按顺序进行执行。其意义在于统一所有的外界任务到一个线程中,使得这些任务之间不需要处理线程同步的问题。

使用方法如下所示:

Runnable myRunnable = new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);
            }
        };


        ExecutorService myFixedThreadPool = Executors.newFixedThreadPool(20);
        myFixedThreadPool.execute(myRunnable);

        ExecutorService myCachedThreadPool = Executors.newCachedThreadPool();
        myCachedThreadPool.execute(myRunnable);

        ScheduledExecutorService myScheduledThreadPool = Executors.newScheduledThreadPool(10);
        //2s后执行runnable
        myScheduledThreadPool.schedule(myRunnable, 2000, TimeUnit.MILLISECONDS);
        //延迟10ms后,每个1000ms执行一次runnable
        myScheduledThreadPool.scheduleAtFixedRate(myRunnable, 10, 1000, TimeUnit.MILLISECONDS);

        ExecutorService mySingleThreadExecutor = Executors.newSingleThreadExecutor();
        mySingleThreadExecutor.execute(myRunnable);