线程池算是Android开发中非常常用的一个东西了,只要涉及到线程的地方,大多数情况下都会涉及到线程池。Android开发中线程池的使用和Java中线程池的使用基本一致。那么今天我想来总结一下Android开发中线程池的使用。
假如说要做一个ListView,ListView上有一个item,每个item上都有一张图片需要从网络上加载,如果不使用线程池,简单的通过下面的方式来开启一个新线程:
new Thread(new Runnable() {
@Override
public void run() {
//网络访问
}
}).start();
这种用法主要存在以下3点问题:
1.针对每一个item都创建一个新线程,这样会导致频繁的创建线程,线程执行完之后又被回收,又会导致频繁的GC
2.这么多线程缺乏统一管理,各线程之间互相竞争,降低程序的运行效率,手机页面卡顿,甚至会导致程序崩溃
3.如果一个item滑出页面,则要停止该item上图片的加载,但是如果使用这种方式来创建线程,则无法实现线程停止执行
如果使用线程池,我们就可以很好的解决以上三个问题:
1.重用已经创建的好的线程,避免频繁创建进而导致的频繁GC
2.控制线程并发数,合理使用系统资源,提高应用性能
3.可以有效的控制线程的执行,比如定时执行,取消执行等
Android中的线程池其实源于Java,都是通过对ThreadPoolExecutor进行不同配置来实现的,那么我们今天就从这这个ThreadPoolExecutor来开始吧!
ThreadPoolExecutor有四个重载的构造方法,我们这里来说说参数最多的那一个重载的构造方法,这样大家就知道其他方法参数的含义了,如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 线程池中核心线程的数量
maximumPoolSize : 线程池中最大线程数量
keepAliveTime: 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
unit: 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
workQueue: 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory: 为线程池提供创建新线程的功能,这个我们一般使用默认即可
handler: 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
附:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
除此之外,还要说的是workQueue是一个BlockingQueue类型,而BlockingQueue是一个特殊的队列,当我们从BlockingQueue中取数据时,如果BlockingQueue是空的,则取数据的操作会进入到阻塞状态,当BlockingQueue中有了新数据时,这个取数据的操作又会被重新唤醒。同理,如果BlockingQueue中的数据已经满了,往BlockingQueue中存数据的操作又会进入阻塞状态,直到BlockingQueue中又有新的空间,存数据的操作又会被冲洗唤醒。
下面说说BlockingQueue的几个常用的实现类:
1.ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。
2.LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE。
3.PriorityBlockingQueue:这个队列和LinkedBlockingQueue类似,不同的是PriorityBlockingQueue中的元素不是按照FIFO来排序的,而是按照元素的Comparator来决定存取顺序的(这个功能也反映了存入PriorityBlockingQueue中的数据必须实现了Comparator接口)。
4.SynchronousQueue:这个是同步Queue,属于线程安全的BlockingQueue的一种,在SynchronousQueue中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous内部没有数据缓存空间,因此我们无法对SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。
线程池工作流程:
1.corePoolSize 也称为工作线程,没有任务到来之前就创建corePoolSize个线程。当一个任务到来的时候,如果任务队列为空,工作线程也有空闲,就启用一个工作线程处理任务。
2.如果工作线程都处于运行的状态,而任务队列workQueue还没有满,则往队列中放入线程信息。
3.如果工作线程都处于运行状态,任务队列又满了,maximumPoolSize大于corePoolSize。就会新建一个非核心进程处理新到来的任务。
4.如果非核心进程在keepAliveTime时间内都处于空闲状态就会被回收掉。
5.或者如果线程池中的线程数已经超过非核心线程数,则拒绝执行该任务。
我们在实际开发中如果需要自己来配置这些参数,该如何配置呢?参考一下AsyncTask,AsyncTask部分源码如下:
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
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());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
....
....
}
核心线程数为手机CPU数量+1(cpu数量获取方式Runtime.getRuntime().availableProcessors()),最大线程数为手机CPU数量×2+1,线程队列的大小为128,OK,那么小伙伴们在以后使用线程池的过程中可以参考这个再结合自己的实际情况来配置参数。
接下来我们就来看看一下四种系统配置好的提供给我们的线程池:
1.FixedThreadPool
FixedThreadPool是一个核心线程和最大线程数量相等的的线程池,因此线程池中的线程数目是一定的,创建方式如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
且线程的超时时间为0,说明核心线程即使在没有任务可执行的时候也不会被销毁
2.SingleThreadExecutor
singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
3.CachedThreadPool
CachedTreadPool一个最大的优势是它可以根据程序的运行情况自动来调整线程池中的线程数量。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
CachedThreadPool中是没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE,另外,它是有线程超时机制的,超时时间为60秒,这里它使用了SynchronousQueue作为线程队列,SynchronousQueue的特点上文已经说过了,这里不再赘述。由于最大线程数为无限大,所以每当我们添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务,如果没有空闲线程,则创建新线程来执行任务。根据CachedThreadPool的特点,我们可以在有大量任务请求的时候使用CachedThreadPool,因为当CachedThreadPool中没有新任务的时候,它里边所有的线程都会因为超时而被终止。
4.ScheduledThreadPool
ScheduledThreadPool是一个具有定时定期执行任务功能的线程池。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
它的核心线程数量是固定的(我们在构造的时候传入的),但是非核心线程是无穷大,当非核心线程闲置时,则会被立即回收。
它拥有几种特殊的启动任务的方式:
1.延迟启动任务:
scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS);
延迟1秒启动任务。
2.延迟定时执行任务:
scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);
延迟1秒之后每隔1秒执行一次新任务。
3.延迟执行任务
scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);
第一次延迟1秒之后,以后每次也延迟1秒执行。
scheduleAtFixedRate周期性的,不管当前任务执行完没有,到了时间就执行下一个任务。
scheduleWithFixedDelay,必须当前任务执行完毕,然后延迟所设置的时间就执行下一个任务。
线程池的常用功能:
1.shutDown() 关闭线程池,停止接收新任务,执行完线程池中的任务和任务队列中的任务才关闭线程池
2.shutDownNow() 关闭线程池,停止接收新任务,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值,submit通常与callable线程相联系。
Android ListView异步加载图片乱序问题,原因分析及解决方案:可以使用AsynTask进行网络图片的下载,而AsynTask的原理就是线程池。所有提交的异步任务都会在这个线程池中的工作线程内执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler。传递消息的方式,调用相关的回调函数,从而实现UI界面的更新。
参考博客:http://m.blog.csdn.net/u012702547/article/details/52259529