本文通过非常通俗易懂的的描述来对AsyncTask扫盲所有关于AsyncTask应该掌握的知识点:
AsyncTask的源码只有400行左右,所以不需要虚,通过源码来解释AsyncTask的使用会带来什么样的坑。
正文:
AsyncTask是线程池和Handler的封装。
关于线程池,AsyncTask内置有两个线程池:
- THREAD_POOL_EXECUTOR(ThreadPoolExecutor)
- SERIAL_EXECUTOR(SerialExecutor)
第一个线程池实例创建的源码:
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
创建第一个线程池的几个参数注释:
// 获取虚拟机可用CPU数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// 线程池核心线程数(最少2个,最多4个)
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
// 线程池最大线程数量(核心线程数 + 其它线程数量)
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
// 线程闲置时间阈值(单位:秒)
// 当线程池中的线程大于corePoolSize时,多余线程空闲时间超过keepAliveTime时,会关闭这部分线程。
private static final int KEEP_ALIVE_SECONDS = 30;
// 线程工厂(用来创建线程的)
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
// 线程池任务队列(就是每次new AsyncTask创建的任务,最多有128个)
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
关于线程池别问什么是不是串行执行、并行执行,如果是串行执行任务,那多线程还有存在的意义么?线程池执行线程里头的任务当然是并发的啦!!!
那为什么有人一会说AsyncTask是串行执行任务的,一会又说它是并行执行任务的呢?对于这个问题,我们先直接看第二个线程池的源码:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
SerialExecutor这个线程池名字取得很好(SerialExecutor:有序的线程池),SerialExecutor说白了就是基于第一个线程池THREAD_POOL_EXECUTOR的二次包装,那它里头干了什么事呢?加了同步锁呗,保证每次我们new AsyncTask并调用execute()时执行的任务是串行的,以及保证操作一些共享变量时线程安全。
看看AsyncTask.execute()的源码:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
通过源码可以知道AsyncTask默认是使用第二个线程池SerialExecutor来执行任务队列里头的任务的。现在知道为什么AsyncTask默认执行任务是串行的了吧。
因为AsyncTask调用execute方法,默认使用SerialExecutor这个线程池,SerialExecutor通过同步关键词synchronized来保证线程执行任务队列里头的任务是串行的。
但是串行有个坑,万一任务队列哪个任务执行crash掉,然后没有注意try catch出来,后面的任务就跟着crash,队友没选好。
Android大法向来做事情都是讲究多线程并发的,AsyncTask.execute()竟然默认是串行,太浪费线程池里头的其他线程资源了,说好的多线程并发呢?
简单,我们不要用第二个线程池SerialExecutor执行AsyncTask的任务呗。用第一个线程池来并发执行我们的任务:
public static final Executor THREAD_POOL_EXECUTOR;
实现代码:
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
// ...
}
});
new AsyncTask<>() {
// ...
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
这样就可以让AsyncTask并发执行任务队列里头的任务了。当然前提就是AsyncTask里头引用的变量是互不干扰的,总不能A线程修和B线程同时修改同一个变量的值吧。
但是,使用第一个线程如果请求的任务太多,一样crash,这又是为什么?
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
// 线程池任务队列(就是每次new AsyncTask创建的任务,最多有128个)
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
通过源码可以看到,sPoolWorkQueue 线程池任务队列最多只能有128个,超过就crash。为什么会这样呢?我们首先要知道一下4点:
-
如果当前线程池中的任务数量小于corePoolSize,则直接创建并添加任务到队列中。
-
如果当前线程池中的任务数量等于corePoolSize,任务队列 workQueue未满,那么任务被放入队列、等待任务调度执行。
-
如果当前线程池中的任务数量大于corePoolSize,任务队列workQueue已满,并且线程池中的数量小于maximumPoolSize,新提交任务则会创建新线程去执行任务。
-
如果当前线程池中的任务数量大于corePoolSize,任务队列workQueue已满,并且线程池中的数量等于maximumPoolSize,则抛异常。
当然,我们也可以构建自己的一个线程池,传入即可,代码实现如下:
int CPU_COUNT = Runtime.getRuntime().availableProcessors();
int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
int KEEP_ALIVE = 30;
BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(200);
ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
String name = "Thread #" + mCount.getAndIncrement();
return new Thread(r, name);
}
};
/* 这种方式创建线程池,可以按需配置 */
// Executor MY_THREAD_POOL
// = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
// TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
/* 这种方式创建线程池,任务队列没有最大阈值,不过内存会hold不住 */
Executor MY_THREAD_POOL = Executors.newScheduledThreadPool(CORE_POOL_SIZE);
for (int i = 0; i < 200; i++) {
MY_THREAD_POOL.execute(new Runnable() {
@Override
public void run() {
// ...
}
});
}
关于AsyncTask的使用,还有需要注意的就是容易导致内存泄露的情况:
1.非静态内部类引用了外部变量导致的内存泄露
2.未执行的AsyncTask导致的内存泄露
第一种情况比较好处理,如果用到了内部类,给内部类添加静态修饰符即可;
第二种情况则需要在界面的生命周期结束的时候,设置任务取消标记,如代码:
static class MyTask extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(String[] objects) {
if (!isCancelled()) {
// ...
}
return null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
task.cancel(true);
}
网友评论