初探Android线程池

前言

最近在看OkHttp的源码,看的时候发现有关线程池的运用,本身就仔细想了一下,这个块知识好像不是很牢固。没办法,再研究一下有关线程池的相关知识吧。学习就是一个查漏补缺的过程,最终的目的仍是要造成本身的知识网络。 网络

为何要使用线程池

平时在Android开发的过程当中常常会用到多线程异步处理相关任务,每开一个线程都要新建一个Thread对象来处理,这种操做会形成哪些后果呢?多线程

一、系统执行多任务时,会为每一个任务建立对应的线程,当任务执行结束以后会销毁对应的线程,在这种状况下对象被频繁的建立和销毁。
二、当对线程象被频繁时会占用大量的系统资源,在并发的过程当中会形成资源竞争出现问题。大量的建立线程还会形成混乱,没有一个统一的管理机制,容易形成应用卡顿。
三、大量线程对象被频繁销毁,将会频繁出发GC机制,从而下降性能。
并发

因为多线程异步处理任务有可能形成这样或者那样的问题,那么线程池应运而生。异步

线程池的做用

咱们来看一下使用线程池的好处:函数

一、重用线程池中的线程,避免因频繁建立和销毁线程形成的性能消耗。
二、更加有效的控制线程的最大并发数,防止线程过多抢占资源形成的系统阻塞。
三、对线程进行有效的管理。
性能

线程池类继承结构

咱们先看一张有关线程池的类继承结构图:学习

Excutor:
 这只是一个接口,其中只定义了一个execute(Runnable command)方法,从接收参数来看只能执行Runnable任务,不能执行Callable带有的返回值的任务。this

ExecutorService:
 这也是一个接口,继承于Excutor。可是它Excutor的基础上添加了管理线程池生命周期的方法shutdown()shutdownNow()。同时,ExecutorService还支执行Callable带有返回值的任务。当提交完任务以后会拿到一个Future返回值,这个返回值表明了任务执行完毕的结果。
shutdown()会等以前的任务执行完毕以后在关闭,同时也不会再接收新任务。若是咱们须要等待线程池处理完成再返回,可使用awaitTermination方法来等待完成。
shutdownNow()方法会尝试立刻关闭全部正在执行的任务,而且跳过全部已经提交可是尚未运行的任务。可是对于正在执行的任务,是否可以成功关闭它是没法保证的,有可能他们真的被关闭掉了,也有可能它会一直执行到任务结束。spa

ThreadPoolExecutor:
 这个类线程池的核心类,咱们这篇文章主要来分析它。线程

ThreadPoolExecutor

构造函数

这个类中有不少构造函数,咱们找一个参数最多的构造函数来看一下。

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
复制代码

corePoolSize

这个参数表示的是核型线程数,当一个请求进来时当前线程池中的线程个数小于核心线程数,能够直接经过ThreadFactory建立线程池;若是已经大于核心线程数时,则将任务放入到workQueue(任务队列)中。

maximumPoolSize

这个参数表示线程池中能够建立的最大线程数。当线程池中的线程数等于corePoolSize而且workQueue(任务队列)已满,这时就要看当前线程数是否大于maximumPoolSize,若是小于则会建立线程去执行任务,不然会时候“饱和策略”去拒绝这个任务请求。对于超过corePoolSize的线程称之为“idle Thread”,这部分线程会有一个最大的空闲存活时间,若是超过这个空闲存活时间尚未任务被分配,则会将这些线程进行回收。

keepAliveTime 和 unit

这两个参数就是用来控制“idle Thread”(空闲线程)的空闲存活时间,u nit表示时间单位,当超过这个时间时将会被回收。在ThreadPoolExecutor中有一个很是重要参数private volatile boolean allowCoreThreadTimeOut,这个参数表示当核心线程超过最大空闲时间还没被分配任务是是否回收,默认返回false,表示不会被回收。若是将这个参数设置成true的话,当核心线程超过最大空闲时间时将会被回收。

workQueue

阻塞队列,当线程数超过corePoolSize的部分任务会被放入到这个队列中等待执行。阻塞队列也会被分红有界和无界,当咱们制定这个队列的capacity时,就是一个有界阻塞队列,反之就是一个无界阻塞队列。当该队列为无界阻塞队列时,会有大量的任务被存入,从而致使内存溢出系统崩溃。

threadFactory

这是一个线程工厂,被用来为线程池建立线程。当咱们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()建立默认的线程工厂,其后续建立的线程优先级都是Thread.NORM_PRIORITY。若是咱们指定线程工厂,咱们能够对产生的线程进行必定的操做。

handler

饱和策略,当线程池达到饱和状态时拒绝多余的任务。ThreadPoolExecutor中有三种饱和策略,AbortPolicy:执行策略时抛出RejectedExecutionException异常。CallerRunsPolicy:不在线程池中运行任务,在调用者的线程中运行任务。DiscardOldestPolicy:将队列中等待最久的直从队列头部移除,将新的任务加入到队列尾部。DiscardPolicy:直接丢弃任务。

执行方法

线程池中有两种执行方法,分别是submit()execute(),下面咱们经过源码看一下二者的区别。

execute()

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get(); //1
        if (workerCountOf(c) < corePoolSize) { //2
            if (addWorker(command, true)) //3
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) { //4
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command)) //5
                reject(command);
            else if (workerCountOf(recheck) == 0) //6
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) //7
            reject(command);
    }
复制代码

在上面有几处注释,咱们看一下。

一、获取当前线程池的状态和有效线程的个数。
二、判断当前线程池中的线程个数是否小于核心线程数。
三、如若没有超过核心线程数,将直接建立核心线程执行任务,若是建立成功直接返回,若是不成功将进行下一步
四、判断当前线程池的状态是否为RUNNING状态,并将任务添加到队列中。
五、查看一下线程池的状态,若是不是RUNNING,直接移除。
六、若是当前线程池中线程数量为0,则单首创建线程,可是不指定任务。
七、若是上述条件都不瞒住,而且建立一个非核心线程来执行任务失败,直接调用reject方法

submit()

/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
    
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
复制代码

submit(...)方法实际上是AbstractExecutorService中的方法,ThreadPoolExecutor继承于它,这里的submit``方法又会调用ThreadPoolExecutorexecute(...)```方法。

线程的池的运行过程

从上面的执行方法咱们能得到下面关于线程池运行过程的图。

线程池的运行过程主要分一下几个步骤:

一、当须要执行的任务被提交到线程池后,首先判断当前运行的线程是否少于corePoolSize。若是小于,则建立新线程来执行任务。
二、若是当前线程池中运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
三、BlockingQueue已满则没法将任务加入,这时就会建立新的线程来处理任务。
四、若是建立新线程会让当前运行的线程数超出maximumPoolSize,拒绝任务,并调用RejectedExecutionHandler.rejectedExecution()方法。

文章开头也讲过,最近在看okhttp源码的时候碰到线程池这个不太熟悉的知识点,就赶忙过来研究一下。因为时间仓促,这篇文章有些简短且浅显,有关线程池深刻的知识将会在后续文章中展现。

参考资料

线程池原理
Java线程池

相关文章
相关标签/搜索