Android中的线程池和AsyncTask异步任务

Android中的线程池和AsyncTask异步任务


一、为什么需要引入线程池?
相信很多人都遇到过大量线程并发执行这种情况,譬如说大量图片的下载。那么问题就来了,对于这种大量的并发任务,若是采用常规的做法,为每一张图片的下载均为之开启一个单独的工作线程,并且在完成图片下载之后销毁这个线程。那么这种情况必然会导致频繁的线程创建和销毁,带来大量的开销,更甚者会因为创建了大量的线程而资源耗尽。因此我们想到,能不能在限制线程数量的同时,对这些线程进行复用呢?于是,线程池就诞生了。
这其实运用到了设计模式中的对象池模式(Object Pool),作用如下:
a.复用线程池中的线程,减少了创建和销毁线程的开销。
b.限制系统中并发线程的数量,防止资源耗尽。
c.对线程进行简单的管理,譬如提供定时执行以及制定间隔循环执行等功能。
 
二、线程池:
1.Android中的资源池其实来源于Java。线程池的实现是ThreadPoolExecutor。Java中有一个Executors工厂,这个工厂类提供了许多静态方法用于创建不同类型的线程池。创建自定义的线程池一般需要指定以下的参数。
a.corePoolSize:代表的是线程池的核心线程数量,一般来说,核心线程会在线程池中一直存活,即便处于空闲状态(没有执行任务的状态)。但是也可以通过将ThreadPoolExecutor的allowCoreThreadTimeOut()设置为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()了多少个任务,事实上都是在同一个线程池中执行的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值