AsyncTask源码解析

本文通过非常通俗易懂的的描述来对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);
    }

猜你喜欢

转载自blog.csdn.net/u014011112/article/details/80189391