博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android中的线程池和AsyncTask异步任务(一)
阅读量:4094 次
发布时间:2019-05-25

本文共 4397 字,大约阅读时间需要 14 分钟。

前言:线程池和AsyncTask是开发中常用的两个工具,那么它们之间到底有什么关系呢?我们应该如何选择呢?接下来让我们一同来探讨。

一、为什么需要引入线程池?

相信很多人都遇到过大量线程并发执行这种情况,譬如说大量图片的下载。那么问题就来了,对于这种大量的并发任务,若是采用常规的做法,为每一张图片的下载均为之开启一个单独的工作线程,并且在完成图片下载之后销毁这个线程。那么这种情况必然会导致频繁的线程创建和销毁,带来大量的开销,更甚者会因为创建了大量的线程而资源耗尽。因此我们想到,能不能在限制线程数量的同时,对这些线程进行复用呢?于是,线程池就诞生了。

这其实运用到了设计模式中的对象池模式(Object Pool),作用如下:

a.复用线程池中的线程,减少了创建和销毁线程的开销。

b.限制系统中并发线程的数量,防止资源耗尽。

c.对线程进行简单的管理,譬如提供定时执行以及制定间隔循环执行等功能。

 

二、线程池:

1.Android中的资源池其实来源于Java。线程池的实现是ThreadPoolExecutorJava中有一个Executors工厂,这个工厂类提供了许多静态方法用于创建不同类型的线程池。创建自定义的线程池一般需要指定以下的参数。

a.corePoolSize:代表的是线程池的核心线程数量,一般来说,核心线程会在线程池中一直存活,即便处于空闲状态(没有执行任务的状态)。但是也可以通过将ThreadPoolExecutorallowCoreThreadTimeOut()设置为true,那么核心线程在空闲的时候会有超时的策略。

b.maximumPoolSize:代表线程池能够容纳的最多的线程数量。当线程数量达到这个数值后,后续的任务将会被阻塞。

c.keepAliveTime:设置非核心线程空闲时的超时时长,一旦达到这个限制,线程就会被回收。当设置ThreadPoolExecutor的allowCoreThreadTimeOut()为true时,这个超时限制也可以作用于核心线程。

d.unit:指定keepAliveTime参数的时间单位。

e.workQueue:代表线程池中的任务队列,通过execute()提交的任务都会加到这个队列中。

f.threadFactory:线程工厂,为线程池提供创建线程的功能。

 

2.ThreadPoolExecutor执行任务时遵循的规则

a.如果线程池中的线程数量少于核心线程,那么直接启动一个核心线程来执行任务。

b.如果线程池中的线程数量大于等于核心线程数量,那么任务会被放入任务队列中进行等候。

c.如果在b中任务队列已满,也就是无法将任务插入队列的时候。若此时线程数量未达maximumPoolSize指定的最大线程数量,那么系统启动非核心线程来执行任务。

d.若是c中线程数量已经达到了maximumPoolSize指定的最大线程数量,那么线程池将会拒绝任务。

 

3.Android中的几种线程池:

a.FixedThreadPool:线程数量固定,并且均为核心线程(也就是没有maximumPoolSize一说)。因为均为核心线程,因此可以快速响应外界请求。没有超时机制,也没有任务队列大小限制。

b.SingleThreadExecutor:相当于前者的简单版,只有一个核心线程,确保了所有的任务串行执行,不需要处理同步的问题。

c.CachedThreadPool:线程数量不固定,并且只有非核心线程的线程池,maximumPoolSize可以任意大(Integer.MAX_VALUE)。每个空闲的线程都会有超时机制。保证了所有的任务都能够立即执行(启动非核心线程),适合于大量的低耗时任务。

d.ScheduledThreadPool:核心线程数量固定,非核心线程数量没有限制,由于执行定时任务和具有固定周期的任务。

 

三、AsyncTask异步任务

1.AsyncTask的简单了解:

AsyncTask异步任务底层是封装了线程池和Handler。被设计用来方便地更新UI。不适合特别耗时的任务(不能多于几s的任务)。本身是一个抽象泛型类。需要继承这个基类才能使用。这个基类中包括几个常用的方法(其中doInBackground()是必须的)。

a.onPreExecute():在UI线程中调用,在doInBackground()之前调用。

b.doInBackground():线程池中调用,执行耗时任务。在这个方法中可以调用publishProgress()方法(会触发onProgressUpdate())以更新任务进度。这个方法的返回值将作为onPostExecute()的参数。

c.onProgressUpdate():主线程中调用。用于更新进度。

d.onPostExecute():主线程中调用,用于返回任务执行结果。

e.onCancelled():当任务取消的时候在主线程中调用,这时候不会调用onPostExecute()。

 

2.AsyncTask一些使用限制:

a.AsyncTask必须在主线程中加载创建,这是为了其中封装的Handler能够正常工作。

b.execute()必须在UI线程中调用

c.一个AsyncTask对象只能够执行一次,即是一次只能调用一次execute()方法,否则报异常。

 

3.AsyncTask的源码剖析(只是想简单使用的可以略过):

AsyncTask底层是基于线程池和Handler的,所以分析离不开之前所说的线程池的一些参数等知识。

1.一些基本的声明

//以下分别是核心线程数,最大线程数,超时限制时间和任务队列的声明(THREAD_POOL_EXECUTOR)。

private static final int CORE_POOL_SIZE = 5;

private static final int MAXIMUM_POOL_SIZE = 128;

private static final int KEEP_ALIVE = 1;

private static final BlockingQueue<Runnable>sPoolWorkQueue =

            new LinkedBlockingQueue<Runnable>(10);

可以看出,相比于前面的提到的系统提供的线程池(要么非核心线程没有限制,要么任务队列没有限制),AsyncTask有个更多的限制

 

2.AsyncTask中的线程池

//以下是是AsyncTask中定义的两个线程池,一个是SerialExecutor,另一个是//THREAD_POOL_EXECUTOR

public static final ExecutorSERIAL_EXECUTOR =new SerialExecutor();

 public static final ExecutorTHREAD_POOL_EXECUTOR

        =new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,  KEEP_ALIVE,TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);

查看分析源码可以发现,SerialExecutor只是用于任务的排队的线程池,而THREAD_POOL_EXECUTOR才是用于执行任务的线程池。具体如下(可跳过):

private static class SerialExecutorimplements Executor {

        final ArrayDeque<Runnable>mTasks =new ArrayDeque<Runnable>();

        RunnablemActive;

//将新传入的一个任务插入mTasks队列中进行排队。

//注意这里并非是线程池的那个任务队列。

//而是相当于我们多次调用execute()提交任务时候的一种缓冲处理,等候进入真正执行任务的线程池。

        public synchronized void execute(final Runnabler) {

            mTasks.offer(new Runnable() {

                public void run() {

                    try {

                        r.run();

                    }finally {

                        scheduleNext();

                    }

                }

            });

            if (mActive ==null) {

                scheduleNext();

            }

        }

//当当前没有活动的任务(Runnable对象)的时候

//通过THREAD_POOL_EXECUTOR执行任务

        protected synchronized void scheduleNext() {

            if ((mActive =mTasks.poll()) !=null) {

                THREAD_POOL_EXECUTOR.execute(mActive);

            }

        }

}

 

3.关于AsyncTask的一个大误区:

前面提到,必须要UI线程中创建AsyncTask子类。执行任务的时候只要new    MyAsyncTask().execute()即可,但是execute()只能执行一次。也就是我们事实上每次仅仅能够通过AsyncTask对象提交一个任务,那么问题来了,既然只能提交一个任务,那么为什么还需要用到线程池来作为底层实现呢?每次new创建一个AsyncTask对象会不会导致说创建多个线程池呢?而且每一个只执行一个任务???!!!那真是太awful了。显然不是这样的。其实这这是一个简单的Java基础的问题,但是往往会被忽略。我们可以看到源码如下:

public static final ExecutorSERIAL_EXECUTOR =new SerialExecutor();

 public static final ExecutorTHREAD_POOL_EXECUTOR

        =new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,  KEEP_ALIVE,TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);

也就是说,这两个线程池采用的都是static类变量声明。也就是所有的实例共用的!因此无论你在UI线程中new了多少个AsyncTask对象,execute()了多少个任务,事实上都是在同一个线程池中执行的。

附:关于AsyncTask串行/并行执行,以及为何不能执行特别耗时任务的说明,可移步下一篇博客《Android中的线程池和AsyncTask异步任务(二)》

转载地址:http://inaii.baihongyu.com/

你可能感兴趣的文章
进程管理(一)
查看>>
linux 内核—进程的地址空间(1)
查看>>
存储器管理(二)
查看>>
开局一张图,学一学项目管理神器Maven!
查看>>
Android中的Binder(二)
查看>>
Framework之View的工作原理(一)
查看>>
Web应用架构
查看>>
设计模式之策略模式
查看>>
深究Java中的RMI底层原理
查看>>
用idea创建一个maven web项目
查看>>
Kafka
查看>>
9.1 为我们的角色划分权限
查看>>
维吉尼亚之加解密及破解
查看>>
DES加解密
查看>>
TCP/IP协议三次握手与四次握手流程解析
查看>>
PHP 扩展开发 : 编写一个hello world !
查看>>
inet_ntoa、 inet_aton、inet_addr
查看>>
用模板写单链表
查看>>
用模板写单链表
查看>>
链表各类操作详解
查看>>