为什么要用线程池

使用线程池管理线程有如下优点:降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池介绍

ThreadPoolExecutor

Java 为我们提供了 ThreadPoolExecutor 来创建一个线程池,其完整构造函数如下所示:public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)int corePoolSize(核心线程数):线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程;核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态);如果设置了 allowCoreThreadTimeOut 为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

int maximumPoolSize(线程池能容纳的最大线程数量):线程总数 = 核心线程数 + 非核心线程数。

long keepAliveTime(非核心线程空闲存活时长):非核心线程空闲时长超过该时长将会被回收,主要应用在缓存线程池中,当设置了 allowCoreThreadTimeOut 为 true 时,对核心线程同样起作用。

TimeUnit unit(keepAliveTime 的单位):它是一个枚举类型,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)。

BlockingQueue workQueue(任务队列):当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务,常用的 workQueue 类型:SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现 线程数达到了 maximumPoolSize 而不能新建线程 的错误,使用这个类型队列的时候,maximumPoolSize 一般指定成 Integer.MAX_VALUE,即无限大。

LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize 的设定失效,因为总线程数永远不会超过 corePoolSize。

ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到 corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误。

DelayQueue:队列内元素必须实现 Delayed 接口,这就意味着你传进去的任务必须先实现 Delayed 接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。ThreadFactory threadFactory(线程工厂):用来创建线程池中的线程,通常用默认的即可。

RejectedExecutionHandler handler(拒绝策略):在线程池已经关闭的情况下和任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务,在上面两种情况下,只要满足其中一种时,在使用 execute() 来提交新的任务时将会拒绝,线程池提供了以下 4 种策略:AbortPolicy:默认策略,在拒绝任务时,会抛出RejectedExecutionException。

CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。

DiscardOldestPolicy:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。

DiscardPolicy:该策略默默的丢弃无法处理的任务,不予任何处理。

线程池执行策略

当一个任务要被添加进线程池时,有以下四种执行策略:线程数量未达到 corePoolSize,则新建一个线程(核心线程)执行任务。

线程数量达到了 corePoolsSize,则将任务移入队列等待。

队列已满,新建非核心线程执行任务。

队列已满,总线程数又达到了 maximumPoolSize,就会由 RejectedExecutionHandler 抛出异常。

其流程图如下所示:

image

常见的四类线程池

常见的四类线程池分别有 FixedThreadPool、SingleThreadExecutor、ScheduledThreadPool 和 CachedThreadPool,它们其实都是通过 ThreadPoolExecutor 创建的,其参数如下表所示:线程池名称corePoolSizemaximumPoolSizekeepAliveTimeunitworkQueuethreadFactoryhandler适用场景FixedThreadPoolnThreadsnThreads0MILLISECONDSLinkedBlockingQueuedefaultThreadFactorydefaultHandler已知并发压力的情况下,对线程数做限制

SingleThreadExecutor110MILLISECONDSLinkedBlockingQueuedefaultThreadFactorydefaultHandler需要保证顺序执行的场景,并且只有一个线程在执行

ScheduledThreadPoolcorePoolSizeInteger.MAX_VALUE10MILLISECONDSDelayedWorkQueuedefaultThreadFactorydefaultHandler需要多个后台线程执行周期任务的场景

CachedThreadPool0Integer.MAX_VALUE60SECONDSSynchronousQueuedefaultThreadFactorydefaultHandler处理执行时间比较短的任务

如果你不想自己写一个线程池,那么你可以从上面看看有没有符合你要求的(一般都够用了),如果有,那么很好你直接用就行了,如果没有,那你就老老实实自己去写一个吧。

线程池工具类封装及使用

为了提升开发效率及更好地使用和管理线程池,我已经为你们封装好了线程工具类----ThreadUtils,依赖 AndroidUtilCode 1.16.0 版本即可使用,其 API 如下所示:isMainThread            : 判断当前是否主线程

getFixedPool            : 获取固定线程池

getSinglePool           : 获取单线程池

getIoPool               : 获取 IO 线程池

getCpuPool              : 获取 CPU 线程池

executeByFixed          : 在固定线程池执行任务

executeByFixedWithDelay : 在固定线程池延时执行任务

executeByFixedAtFixRate : 在固定线程池按固定频率执行任务

executeBySingle         : 在单线程池执行任务

executeBySingleWithDelay: 在单线程池延时执行任务

executeBySingleAtFixRate: 在单线程池按固定频率执行任务

executeByIo             : 在 IO 线程池执行任务

executeByIoWithDelay    : 在 IO 线程池延时执行任务

executeByIoAtFixRate    : 在 IO 线程池按固定频率执行任务

executeByCpu            : 在 CPU 线程池执行任务

executeByCpuWithDelay   : 在 CPU 线程池延时执行任务

executeByCpuAtFixRate   : 在 CPU 线程池按固定频率执行任务

executeByCustom         : 在自定义线程池执行任务

executeByCustomWithDelay: 在自定义线程池延时执行任务

executeByCustomAtFixRate: 在自定义线程池按固定频率执行任务

cancel                  : 取消任务的执行

如果你使用 RxJava 很 6,而且项目中已经使用了 RxJava,那么你可以继续使用 RxJava 来做线程切换的操作;如果你并不会 RxJava 或者是在开发 SDK,那么这个工具类再适合你不过了,它可以为你统一管理线程池的使用,不至于让你的项目中出现过多的线程池。

ThreadUtils 使用极为方便,看 API 即可明白相关意思,Fixed、Single、Io 分别对应了上面介绍的 FixedThreadPool、SingleThreadExecutor、CachedThreadPool 这三种,CPU 的话就是会建立和设备可使用 CPU 数目相关的线程池,参考于 AsyncTask 的实现;而所有的 execute 都是线程池外围裹了一层 ScheduledThreadPool,这里和 RxJava 线程池的实现有所相似,可以更方便地提供延时任务和固定频率执行的任务,当然也可以更方便地取消任务的执行,下面让我们来简单地来介绍其使用,以从 assets 中拷贝 APK 到 SD 卡为例,其代码如下所示:public static void releaseInstallApk(final OnReleasedListener listener) {    if (!FileUtils.isFileExists(Config.TEST_APK_PATH)) {

ThreadUtils.executeByIo(new ThreadUtils.SimpleTask() {            @Override

public Void doInBackground() throws Throwable {

ResourceUtils.copyFileFromAssets("test_install", Config.TEST_APK_PATH);                return null;

}            @Override

public void onSuccess(Void result) {                if (listener != null) {

listener.onReleased();

}

}

});

} else {        if (listener != null) {

listener.onReleased();

}

LogUtils.d("test apk existed.");

}

}

看起来还不是很优雅是吧,你可以把相关的 Task 都抽出来放到合适的包下,这样每个 Task 的指责一看便知,如上例子可以改装成如下所示:public class ReleaseInstallApkTask extends ThreadUtils.SimpleTask {    private OnReleasedListener mListener;    public ReleaseInstallApkTask(final OnReleasedListener listener) {

mListener = listener;

}    @Override

public Void doInBackground() throws Throwable {

ResourceUtils.copyFileFromAssets("test_install", Config.TEST_APK_PATH);        return null;

}    @Override

public void onSuccess(Void result) {        if (mListener != null) {

mListener.onReleased();

}

}    public void execute() {

ThreadUtils.executeByIo(this);

}

}public static void releaseInstallApk(final OnReleasedListener listener) {    if (!FileUtils.isFileExists(Config.TEST_APK_PATH)) {        new ReleaseInstallApkTask(listener).execute();

} else {        if (listener != null) {

listener.onReleased();

}

LogUtils.d("test apk existed.");

}

}

是不是瞬间清爽了很多,如果执行成功的回调中涉及了 View 相关的操作,那么你需要在 destroy 中取消 task 的执行哦,否则会内存泄漏哦,继续你上面的例子为例,代码如下所示:public class XXActivity extends Activity {

···

@Override

protected void onDestroy() {        // ThreadUtils.cancel(releaseInstallApkTask); 或者下面的取消都可以

releaseInstallApkTask.cancel();        super.onDestroy();

}

}

以上是以 SimpleTask 为例,Task 的话会多两个回调,onCancel() 和 onFail(Throwable t),它们和 onSuccess(T result) 都是互斥的,最终回调只会走它们其中之一,并且在 Android 端是发送到主线程中执行,如果是 Java 端的话那就还是会在相应的线程池中执行,这点也方便了我做单元测试。

线程池工具类单元测试

如果遇到了异步的单测,你会发现单测很快就跑完呢,并没有等待我们线程跑完再结束,我们可以用 CountDownLatch 来等待线程的结束,或者化异步为同步的做法,这里我们使用 CountDownLatch 来实现,我进行了简单的封装,测试 Fixed 的代码如下所示:public class ThreadUtilsTest {    @Test

public void executeByFixed() throws Exception {

asyncTest(10, new TestRunnable() {            @Override

public void run(final int index, CountDownLatch latch) {                final TestTask task = new TestTask(latch) {                    @Override

public String doInBackground() throws Throwable {

Thread.sleep(500 + index * 10);                        if (index

} else if (index

cancel();                            return null;

} else {                            throw new NullPointerException(String.valueOf(index));

}

}                    @Override

void onTestSuccess(String result) {

System.out.println(result);

}

};

ThreadUtils.executeByFixed(3, task);

}

});

}    @Test

public void executeByFixedWithDelay() throws Exception {

asyncTest(10, new TestRunnable() {            @Override

public void run(final int index, CountDownLatch latch) {                final TestTask task = new TestTask(latch) {                    @Override

public String doInBackground() throws Throwable {

Thread.sleep(500);                        if (index

} else if (index

cancel();                            return null;

} else {                            throw new NullPointerException(String.valueOf(index));

}

}                    @Override

void onTestSuccess(String result) {

System.out.println(result);

}

};

ThreadUtils.executeByFixedWithDelay(3, task, 500 + index * 10, TimeUnit.MILLISECONDS);

}

});

}    @Test

public void executeByFixedAtFixRate() throws Exception {

asyncTest(10, new TestRunnable() {            @Override

public void run(final int index, CountDownLatch latch) {                final TestScheduledTask task = new TestScheduledTask(latch, 3) {                    @Override

public String doInBackground() throws Throwable {

Thread.sleep(500 + index * 10);                        if (index

} else if (index

cancel();                            return null;

} else {                            throw new NullPointerException(String.valueOf(index));

}

}                    @Override

void onTestSuccess(String result) {

System.out.println(result);

}

};

ThreadUtils.executeByFixedAtFixRate(3, task, 3000 + index * 10, TimeUnit.MILLISECONDS);

}

});

}    abstract static class TestScheduledTask extends ThreadUtils.Task {        private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();        private int mTimes;

CountDownLatch mLatch;

TestScheduledTask(final CountDownLatch latch, final int times) {

mLatch = latch;

mTimes = times;

}        abstract void onTestSuccess(T result);        @Override

public void onSuccess(T result) {

onTestSuccess(result);            if (ATOMIC_INTEGER.addAndGet(1) % mTimes == 0) {

mLatch.countDown();

}

}        @Override

public void onCancel() {

System.out.println(Thread.currentThread() + " onCancel: ");

mLatch.countDown();

}        @Override

public void onFail(Throwable t) {

System.out.println(Thread.currentThread() + " onFail: " + t);

mLatch.countDown();

}

}    abstract static class TestTask extends ThreadUtils.Task {

CountDownLatch mLatch;

TestTask(final CountDownLatch latch) {

mLatch = latch;

}        abstract void onTestSuccess(T result);        @Override

public void onSuccess(T result) {

onTestSuccess(result);

mLatch.countDown();

}        @Override

public void onCancel() {

System.out.println(Thread.currentThread() + " onCancel: ");

mLatch.countDown();

}        @Override

public void onFail(Throwable t) {

System.out.println(Thread.currentThread() + " onFail: " + t);

mLatch.countDown();

}

}

void asyncTest(int threadCount, TestRunnable runnable) throws Exception {

CountDownLatch latch = new CountDownLatch(threadCount);        for (int i = 0; i

runnable.run(i, latch);

}

latch.await();

}    interface TestRunnable {        void run(final int index, CountDownLatch latch);

}

}

最后想说的话

感谢大家一起陪伴 AndroidUtilCode 的成长,核心工具类几乎都已囊括,也是汇集了我大量的心血,把开源做到了极致,希望大家可以用的舒心,大大提升开发效率,早日赢取白富美,走上人生巅峰。

后文再添加一个个人对 OkHttp 的线程池的使用分析,算是送上个小福利。

OkHttp 中的线程池使用

查看 OkHttp 的源码发现,不论是同步请求还是异步请求,最终都是交给 Dispatcher 做处理,我们看下该类和线程池有关的的主要代码:public final class Dispatcher {  // 最大请求数

private int maxRequests = 64;  // 相同 host 最大请求数

private int maxRequestsPerHost = 5;  // 请求执行线程池,懒加载

private @Nullable ExecutorService executorService;  // 就绪状态的异步请求队列

private final Deque readyAsyncCalls = new ArrayDeque<>();  // 运行中的异步请求队列,包括还没完成的请求

private final Deque runningAsyncCalls = new ArrayDeque<>();  public Dispatcher(ExecutorService executorService) {      this.executorService = executorService;

}  public Dispatcher() {

}  public synchronized ExecutorService executorService() {      if (executorService == null) {          // 和 CachedThreadPool 很相似

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,          new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));

}      return executorService;

}  synchronized void enqueue(AsyncCall call) {    // 不超过最大请求数并且不超过 host 最大请求数

if (runningAsyncCalls.size()

runningAsyncCalls.add(call);      // 添加到线程池中运行

executorService().execute(call);

} else {      // 添加到就绪的异步请求队列

readyAsyncCalls.add(call);

}

}  // 当该异步请求结束的时候,会调用此方法,用于将运行中的异步请求队列中的该请求移除并调整请求队列

// 此时就绪队列中的请求就可以进入运行中的队列

void finished(AsyncCall call) {

finished(runningAsyncCalls, call, true);

}  private  void finished(Deque calls, T call, boolean promoteCalls) {      int runningCallsCount;

Runnable idleCallback;      synchronized (this) {          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");          if (promoteCalls) promoteCalls();

runningCallsCount = runningCallsCount();

idleCallback = this.idleCallback;

}      if (runningCallsCount == 0 && idleCallback != null) {

idleCallback.run();

}

}  // 根据 maxRequests 和 maxRequestsPerHost 来调整 runningAsyncCalls 和 readyAsyncCalls

// 使运行中的异步请求不超过两种最大值,并且如果队列有空闲,将就绪状态的请求归类为运行中。

private void promoteCalls() {    // 如果运行中的异步队列不小于最大请求数,直接返回

if (runningAsyncCalls.size() >= maxRequests) return;    // 如果就绪队列为空,直接返回

if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

// 遍历就绪队列并插入到运行队列

for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {

AsyncCall call = i.next();      if (runningCallsForHost(call)

i.remove();

runningAsyncCalls.add(call);

executorService().execute(call);

}      // 运行队列中的数量到达最大请求数,直接返回

if (runningAsyncCalls.size() >= maxRequests) return;

}

}

}

可以发现 OkHttp 不是在线程池中维护线程的个数,线程是通过 Dispatcher 间接控制,线程池中的请求都是运行中的请求,这也就是说线程的重用不是线程池控制的,通过源码我们发现线程重用的地方是请求结束的地方 finished(AsyncCall call) ,而真正的控制是通过 promoteCalls 方法, 根据 maxRequests 和 maxRequestsPerHost 来调整 runningAsyncCalls 和 readyAsyncCalls,使运行中的异步请求不超过两种最大值,并且如果队列有空闲,将就绪状态的请求归类为运行中。

作者:Blankj

链接:https://www.jianshu.com/p/f667f86fa4bb

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

android自定义线程池工具类,妈妈再也不用担心你不会使用线程池了(ThreadUtils)...相关推荐

  1. 细数Android开发者的艰辛历程,妈妈再也不用担心我找工作了!

    目录 想要成为一名优秀的Android开发,你需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样. PagerAdapter 介绍 ViwePager 缓存策略 ViewPager 布局处 ...

  2. 自从用了这款黑科技工具,妈妈再也不用担心我的c盘文件爆满了

    软件安装 使用教程 WinDirStat 是Windows平台下硬盘空间状态分析和清理工具.通过这个工具,可以对指定硬盘分区空间大小进行状态分析,分析完毕之后,对不同文件按颜色和大小进行区隔显示,为清 ...

  3. 【PC工具】chrome浏览器插件vimium:传说上网可以不用鼠标。VIM入门工具,妈妈再也不用担心我学不会vim了...

    vim是一个神奇的编辑器,很多大神都用这个编辑器,大神们一般不太爱不用鼠标,如果运气好可以看到大神写代码,那简直是快的飞起的感觉. 想当年我也励志要学习使用vim,脑子不太好记不住快捷键,加上写程序一 ...

  4. 漏洞扫描工具大全,妈妈再也不用担心我挖不到漏洞了

    漏洞扫描工具大全,妈妈再也不用担心我挖不到漏洞了 1.常见漏洞扫描工具 2.AWVS 3.AppScan 4.X-ray 5.Goby 6.Goby联动Xray 7.Goby联动御剑 1.常见漏洞扫描 ...

  5. Java实现印刷体转手写体—妈妈再也不用担心我被罚抄作业了

    Java实现印刷体转手写体-妈妈再也不用担心我被罚抄作业了 文章目录 Java实现印刷体转手写体-妈妈再也不用担心我被罚抄作业了 缘起 开始开发 测试效果 开源地址和总结 郑重声明 因本文中涉及到爬虫 ...

  6. android 傻瓜式 MultiDex 插件,从此再也不用担心方法数问题!

    ndroid-Easy-MultiDex 项目地址:TangXiaoLv/Android-Easy-MultiDex 简介:Android 傻瓜式 MultiDex 插件,从此再也不用担心方法数问题! ...

  7. 妈妈再也不用担心我的公式写不出来了:一款公式输入神器实测

    本文推荐一个公式输入神器,只要截图就能识别公式,手写的公式都能识别.经过实测,几乎没有识别不出的公式,并可以输入到word.markdown.latex文件. 一.前言 写论文.博客,技术文档,公式输 ...

  8. 《妈妈再也不用担心我的学习系列》之RabbitMQ动态修改队列名

    上一篇文章:<妈妈再也不用担心我的学习系列>之RabbitMQ快速入门 前言 在我们公司日常用RabbitMQ的时候如果多个服务都用到了队列,那么势必会有很多影响(如果不是广播模式队列是轮 ...

  9. 妈妈再也不用担心别人问我是否真正用过redis了

    1. Memcache与Redis的区别 1.1. 存储方式不同 1.2. 数据支持类型 1.3. 使用底层模型不同 2. Redis支持的数据类型 3. Redis的回收策略 4. Redis小命令 ...

最新文章

  1. 技术图文:NumPy 的简单入门教程
  2. phpinfo.php ctf,这你不是你所常见的PHP文件包含漏洞(利用phpinfo)
  3. jsp页面修改成html页面
  4. OpenSSL生成自签名的sha256泛域名证书
  5. linux查找特定日期之后的文件,Linux Find命令查找指定时间范围内的文件的例子
  6. SQL_server_2000安装过程指导
  7. mysql 压力测试知乎_MySQL性能基准测试对比:MySQL 5.7与MySQL 8.0
  8. redis版本_全球首发|阿里云正式推出云数据库Redis6.0版本
  9. SOAP的Could not connect to host in...报错解决方案
  10. 从零开始学架构2 - 高性能篇
  11. APK安装过程及原理详解
  12. Powershell为接收连接器批量添加RemoteIP地址
  13. OpenCV图像处理(6)——轮廓标记
  14. dbmsjobs记录表 oracle_【Oracle学习笔记】定时任务(dbms_job)
  15. webpack配置报错WARNING in DefinePlugin Conflicting values for ‘process.env.NODE_ENV‘
  16. 状态空间方程的等价问题
  17. Qt css样式大全(整理版)
  18. 服务器搭建centos7系统操作过程(使用系统盘搭建centos7系统)
  19. 为什么打开服务器word文档是只读,以只读方式打开Word文档的方法
  20. 使用Arcgis Pro 2.5生成地图文件(tpk,mmpk)

热门文章

  1. wireshare capture filter捕捉过滤的设置
  2. [转]学习Objective-C: 入门教材
  3. 【许晓笛】 EOS智能合约案例解析(1)
  4. Spring系列之Spring框架和SpringAOP集成过程分析(十)
  5. Linux 最新SO_REUSEPORT特性
  6. 转 Android自动测试之monkeyrunner工具(二)
  7. php BC高精确度函数库
  8. php 调用vs2013 dll文件,VScode能编辑DLL库文件么
  9. python lambda_Python 匿名函数 lambda
  10. php smarty继承,PHP Smarty 模板 模板继承 {extends}