本文共 4397 字,大约阅读时间需要 14 分钟。
相信很多人都遇到过大量线程并发执行这种情况,譬如说大量图片的下载。那么问题就来了,对于这种大量的并发任务,若是采用常规的做法,为每一张图片的下载均为之开启一个单独的工作线程,并且在完成图片下载之后销毁这个线程。那么这种情况必然会导致频繁的线程创建和销毁,带来大量的开销,更甚者会因为创建了大量的线程而资源耗尽。因此我们想到,能不能在限制线程数量的同时,对这些线程进行复用呢?于是,线程池就诞生了。
这其实运用到了设计模式中的对象池模式(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()了多少个任务,事实上都是在同一个线程池中执行的。
附:关于AsyncTask串行/并行执行,以及为何不能执行特别耗时任务的说明,可移步下一篇博客《Android中的线程池和AsyncTask异步任务(二)》
转载地址:http://inaii.baihongyu.com/