之前我们说过关于线程池的问题,我们可以用Executors的各种方法来获取不同的ThreadPoolExecutor来满足需求。但是当我们需要自定义线程池的时候需要注意些什么呢?
ThreadPoolExecutor的构造方法有几个用的多的参数,它们的含义分别是 · corePoolSize:线程池的基本大小 · maximumPoolSize:当任务队列满时允许扩展到的线程池的线程数量 · workQueue:存放任务队列的BlockingQueue · handler:当任务队列满时的处理策略
之前说过可以给AsyncTask指定线程池,我们用不同的参数构造线程池来验证效果。
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会有个问题,因为它的存储方式是数组,需要一开始就指定大小,所以必须得指定 handler来做队列满时的处理策略。 如果不指定handler的话只要任务数量超过 ArrayBlockingQueue 指定的大小,就会抛出 RejectedExecutionException异常。
其实 AynscTask 默认会有个线程池,这个线程池的大小是128,可以从源码看出来
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
所以如果用默认的AsyncTask来处理大量的任务的话是有可能导致应用崩溃的。