关于ThreadPoolExecutor要注意的问题

之前我们说过关于线程池的问题,我们可以用Executors的各种方法来获取不同的ThreadPoolExecutor来满足需求。但是当我们需要自定义线程池的时候需要注意些什么呢?

ThreadPoolExecutor的参数含义

ThreadPoolExecutor的构造方法有几个用的多的参数,它们的含义分别是 · corePoolSize:线程池的基本大小 · maximumPoolSize:当任务队列满时允许扩展到的线程池的线程数量 · workQueue:存放任务队列的BlockingQueue · handler:当任务队列满时的处理策略

之前说过可以给AsyncTask指定线程池,我们用不同的参数构造线程池来验证效果。

LinkedBlockingQueue

public class DemoAsyncTask extends AsyncTask {
    private static final String TAG = "DemoAsyncTask";
    private static BlockingQueue<Runnable> blockingQueue = new LinkedBlockingDeque<>(3);

    private static Executor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 0,
            TimeUnit.MICROSECONDS, blockingQueue);

    @Override
    protected Object doInBackground(Object[] objects) {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d(TAG, "doInBackground: num: " + objects[0]);
        return null;
    }

    public void demoExecute(String... params) {
        this.executeOnExecutor(threadPoolExecutor, params);
    }
}

指定一个LinkedBlockingQueue给线程池,然后我们从外部调用,

for(int i = 0; i < 50; i ++) {
  new DemoAsyncTask().demoExecute(String.valueOf(i));
}

这段代码运行就会崩溃,原因是

我们给线程池分配了50个任务,但是任务队列最大只能存放3个任务,当队列满时,系统会抛出RejectedExecutionException异常

解决这个问题有两种方法,一种是不给LinkedBlockingQueue指定队列大小。因为默认情况下 LinkedBlockingDeque会让队列不断增长以存放新进来的数据。这种方式能让50个任务正常进行。

另外一种是用 handler 参数指定队列满时的处理策略,代码可以改成下面这样

private static Executor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 0,
        TimeUnit.MICROSECONDS, blockingQueue, new ThreadPoolExecutor.DiscardOldestPolicy());

这是ThreadPoolExecutor的其中一个默认策略,我们直接看输出就明白了,

04-19 12:42:35.991 6167-6199/com.phoenix.mkdirdemo D/DemoAsyncTask: doInBackground: num: 2 04-19 12:42:35.991 6167-6198/com.phoenix.mkdirdemo D/DemoAsyncTask: doInBackground: num: 1 04-19 12:42:35.991 6167-6196/com.phoenix.mkdirdemo D/DemoAsyncTask: doInBackground: num: 0 04-19 12:42:36.991 6167-6199/com.phoenix.mkdirdemo D/DemoAsyncTask: doInBackground: num: 47 04-19 12:42:36.991 6167-6198/com.phoenix.mkdirdemo D/DemoAsyncTask: doInBackground: num: 48 04-19 12:42:36.991 6167-6196/com.phoenix.mkdirdemo D/DemoAsyncTask: doInBackground: num: 49

所以就是说,这个策略会把队列里老的请求丢弃,保留最新请求。因为一开始的任务0-2正在执行中,而队列已满,因此最终只能保留47-49最后三个请求。这个策略适合用在fast fail场景,快速的反馈给用户失败而不是让用户等待。

ArrayBlockingQueue

用 ArrayBlockingQueue会有个问题,因为它的存储方式是数组,需要一开始就指定大小,所以必须得指定 handler来做队列满时的处理策略。 如果不指定handler的话只要任务数量超过 ArrayBlockingQueue 指定的大小,就会抛出 RejectedExecutionException异常。

回到 AsyncTask

其实 AynscTask 默认会有个线程池,这个线程池的大小是128,可以从源码看出来

private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

所以如果用默认的AsyncTask来处理大量的任务的话是有可能导致应用崩溃的。

原文发布于微信公众号 - Android每日一讲(gh_f053f29083b9)

原文发表时间:2018-03-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏水击三千

Android Geocoder(位置解析)

Android中提供GPS定位服务,同时开发者可以对获得的位置信息进行解析,可以获得位置的详细信息。 1.gps定位 在Eclipse中建立android应用程...

28710
来自专栏猿天地

面试-线程池的成长之路

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

1672
来自专栏Android 开发学习

JsBridge 源码分析

1733
来自专栏逸鹏说道

我这么玩Web Api(二)

数据验证,全局数据验证与单元测试 目录 一、模型状态 - ModelState 二、数据注解 - Data Annotations 三、自定义数据注解 四、全局...

5226
来自专栏编码小白

tomcat请求处理分析(一) 启动container实例

1.1.1  启动container实例 其主要是进行了生命周期中一系列的操作之后调用StandardEngine中的 startInternal方法,不难看出...

3796
来自专栏Java架构师学习

Zookeeper-watcher机制源码分析(二)

其大致流程如下   ① 通过传入的path(节点路径)从watchTable获取相应的watcher集合,进入②

1461
来自专栏小樱的经验随笔

HDU 1232 畅通工程

畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java...

32410
来自专栏技术小黑屋

Android中Handler引起的内存泄露

在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。

1372
来自专栏何俊林

LeakCanary的原理,你知道么?

2812
来自专栏salesforce零基础学习

salesforce零基础学习(七十五)浅谈SOSL(Salesforce Object Search Language)

在工作中,我们更多操作的是一个表的对象,所以我们对SOQL的使用很多。但是有时候,我们需要对几个表进行查询操作,类似salesforce的全局搜索功能,这时,使...

2395

扫码关注云+社区

领取腾讯云代金券