美文网首页
了解AsycnTask就这么简单

了解AsycnTask就这么简单

作者: Ricky_Zeng | 来源:发表于2018-04-22 16:11 被阅读0次

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

    相关文章

      网友评论

          本文标题:了解AsycnTask就这么简单

          本文链接:https://www.haomeiwen.com/subject/lrwqlftx.html