一、开发中异步任务和多线程
1.一贯做法
android日常开发中经常会遇到异步任务和多线程,而我们一贯的做法是,new Thread().start()+Handler,要么就是AysncTask,虽然简单快捷,但是会有很多弊端。
2.弊端
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导
致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
3.引入线程池
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵
塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
从某位博客大大那里借鉴来的
二、Java/Android线程池及用法
java的线程池框架在“java.util.concurrent”包中,打开api会发现,其中的内容是相当可观的。。。
1.顶级接口Executor
其中线程池的顶级接口是“Executor”,从严格意义上讲Executor并不是线程池,其内部只提供一个方法:
Executor接口并不是严格的要求execute()方法异步执行,实现类完全可以在调用线程执行command任务,例如:
public classMyExcutor implementsExecutor {
@Override
public voidexecute(Runnable runnable) {
//在调用线程执行该任务
runnable.run();
//新开线程,异步执行该任务
newThread(runnable).start();
}
}
2.线程池接口ExecutorService和ScheduledExecutorService
为什么要讲到这两个接口?
可以毫不夸张的说,这两个接口是线程池接口的祖宗级别,已经具有线程池的模型规范,各式各样的线程池类都是实现了这两个类,并进行扩展的。
1. ExecutorService
ExecutorService继承Executor接口,并进行了扩展,可以对执行的任务进行管理,如跟踪执行结果、添加、取消、关闭等功能,基本提供了实现了线程池所需的规范。可以通过实现扩展此接口达到想要的线程池功能。
重要的方法:
void | shutdown() :终止前允许之前已提交的任务正常执行 |
List<Runnable> | shutdownNow():拒绝添加任务,并试着中断正在执行的任务和等待执行的任务,返回未执行的任务列表 |
T Futrue<T> | submit(Callable<T> task):向线程池添加一个任务,并返回Futrue句柄,用于跟踪和控制任务task,注:此方法是异步执行,但是也可以阻塞执行: |
Futrue<?> | submit(Runnable task):向线程池添加一个任务,并返回Futrue句柄,用于 |
T List<Future<T>> | invokeAll(Collection<? extends Callable<T>> tasks):执行task集合中的所有任务,并返回执行结果,会阻塞调用线程。 |
2. SheduledExecutorService
● public interface ScheduledExecutorService extends ExecutorService
SheduleExecutorService继承了ExecutorSerrvice接口,并定义延时和定期执行给定任务的规范,供实现类使用并扩展。
重要方法:
<V> ScheduledFuture<V> | schedule(Callable<V> callable, long delay,TimeUnit unit) 创建并执行在给定延迟后启用的ScheduledFuture。 |
schedule(Runnable command, long delay,TimeUnit unit) 创建并执行在给定延迟后启用的单次操作。 | |
scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit) 创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后是initialDelay+period ,然后是initialDelay + 2 * period ,等等。 |
3. 真正的线程池实现类ThreadPoolExecutor(常用)
1.继承关系
public class ThreadPoolExecutor extends AbstractExecutorService
ThreadPoolExecutor类继承自抽象类AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口,但是对于ExecutorService接口的扩展并不多,仅仅多了2个方法,有兴趣可以自己研究一下,在这里不在讲解。
A. 使用ThreadPoolExecutor需要进行一系列的复杂配置,api文档中强烈推荐Executors类进行配置不同功能的ThreadPoolExecutor线程池(Executors是一个java提供的线程池工具类,后续会讲到)。如果Executors不能满足你的需求,api中也提供了手动配置指南,必须严格的按照官方指南配置,api文档中有详细配置指南。
B. ThreadPoolExecutor解决两个问题:
1. 对于执行大量异步任务时,通过减少任务的调用开销而改善性能。
2. 提供任务管理和任务统计功能。
C. 使用线程池,首先需要理解构造方法中的几个参数的含义:
2.ThreadPoolExecutor最简单的构造函数:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)
corePoolSize:核心池大小,即使线程池空闲,一会保留的线程数,除非主动调用
allowCoreThreadTimeOut()方法(如果超过定义的空闲时间,核心线程也会被销毁)。
maximumPoolSize:线程池允许创建的最大线程数。如果添加的任务超过核心线程,
则创建新的非核心线程,但不不会超过maximumPoolSize
keepAliveTime:当前线程数大于核心线程数的时候,如果线程空闲时间超过
keepAliveTime,则终止线程,知道线程数小于核心线程数
unit:keepAliveTime的时间单位
workQueue:用于保存任务的队列,仅保存execute方法提交的Runnable任务(workQueue在源码中声明为:private BlockQueue<Runnbale> workQueue) 。
BlockQueue是一个队列接口,常用的实现子类有:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue
ArrayBlockingQueue:是一个经典的有界队列缓冲区(容量固定,不可扩充)
LinkedBlockingQueue:是无边界的缓冲队列,内次插入节点都是动态创建新节点,直到耗尽系统资源。
此处有个小问题:线程池中可通过submit()和execute()提交任务,workQueue队列仅仅保存execute方法提交Runnbale任务,而submit可提交Runnbale和Callable两种类型的任务,难道线程池不会讲Callable任务添加进队列?那岂不是submit提交任务无法管理了?
答案是否定的,通过源码会发现submit()内部也是调用的execute()方法
public <T> Future<T> submit(Callable<T> var1) {
if(var1 ==null) {
throw newNullPointerException();
}else {
// 此行代码将Callable转为了RunnbaleFuture
RunnableFuture var2 = this.newTaskFor(var1);
//内部调用了execute方法
this.execute(var2);
return var2;
}
}
从源码可看出,submit内部调用了execute方法,并将Callable接口转换为Runnble接口(RunnableFuture实现了Runnble接口)。
知识小扩展:其实ThreadPoolExecutor(线程池)为了管理任务,内部统一将任务包装成Callable的形式,以便可以拿到Future,进行取结果、或终止、或监听状态。那么这将设计一个问题,怎么将Runnable包装成Callable?我们都知道,java/android实现多任务的并发执行,并和用户有良好交互的方法有3种,Thread、Runnable、Callable,在此不深入讲解,有兴趣可以研究遗留的一个问题,能够更清晰的认识Thread、Runnbale、Callable三者的区别和用法。
3.自定义线程池用到的构造函数
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
此构造函数,比上文讲解的多了2个参数,ThreadFactory和RjectedExecutionHandler
ThreadFactory:线程池创建执行任务的线程用的,如果自定义可以,加入一些自己的参数,如线程id,优先级等。
RjectedExecutionHandler:一个处理添加任务过量的机制,java提供了4个实现子类:
ThreadPoolExecutor.AbortPolicy:拒绝添加任务,并抛出异
常
ThreadPoolExecutor.CallerRunsPolicy:拒绝添加任务,并在
调用线程执行任务
ThreadPoolExecutor.DiscardOldestPolicy:拒绝最旧未处理
任务,然后重新添加
ThreadPoolExecutor.DiscardPolicy:拒绝添加任务,并不做
处理
4.常用方法介绍
void | allowsCoreThreadTimeOut(boolean value) 允许核心线程超时,如果超出配置的线程空闲时间,核心线程也将被停止 |
void | 提交任务 |
protected void | finalize() 当这个执行器不再被引用并且没有线程时,调用 shutdown 。 |
int | 返回正在执行任务的线程的大概数量。 |
long | 返回完成执行的任务的大致总数。 |
int | 返回核心线程数。 |
long | getKeepAliveTime(TimeUnit unit) 返回线程保持活动时间,这是超过核心池大小的线程在终止之前可能保持空闲的时间量。 |
int | 返回允许的最大线程数。 |
int | 返回池中当前的线程数。 |
getQueue() 返回此执行程序使用的任务队列。拿到此队列尽量不要做其他的不必要操作,否则会影响线程池运行。 | |
long | 返回计划执行的任务的大概总数。 |
返回用于创建新线程的线程工厂。配置线程池是传入的Threadfactory | |
boolean | 如果此执行者已关闭,则返回 true 。 |
boolean | 如果所有任务在关闭后完成,则返回 true 。 |
boolean | 如果此执行者在 shutdown()或 shutdownNow()之后 终止 ,但尚未完全终止,则返回true。 |
int | 启动所有核心线程,导致他们等待工作。 注:默认有任务添加时,才会创建线程,如果没有任务,是不开启线程的,此方法强制开启配置数量的线程 |
boolean | 启动核心线程,使其无法等待工作。 |
void | purge() 尝试从工作队列中删除已取消的所有Future任务。 |
boolean | 如果此任务存在,则从执行程序的内部队列中删除此任务,从而导致该任务尚未运行。 |
void | setCorePoolSize(int corePoolSize) 设置核心线程数。 |
void | setKeepAliveTime(long time,TimeUnit unit) 设置线程在终止之前可能保持空闲的时间限制。 |
void | setMaximumPoolSize(int maximumPoolSize) 设置允许的最大线程数。 |
void | setThreadFactory(ThreadFactory threadFactory) 设置用于创建新线程的线程工厂。 |
void | shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 |
尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。 |
4. java提供的线程池工具类Executors
自定义线程池(手动配置),还是一件比较麻烦的事情,java提供了一个工具类Excutors(上文也有提及,Executors就是一个提供配置好的线程池的工具类,都是通过直接或间接的配置ThreadPoolExecutor实现的),其中提供了4种不同功能的线程池(基本能满足日常开发所需),并且java官方力荐使用此工具类,并不鼓励自定义线程池。
1. Executors.newFixThreadPool
源码:
public staticExecutorService newFixedThreadPool(intvar0) {
return newThreadPoolExecutor(var0,var0,0L,
TimeUnit.MILLISECONDS, newLinkedBlockingQueue());
}
从源码可以看到,FixThreadPool的核心池和最大池一样,也就是说,它只有核心线程池,而且核心线程也不会被销毁,会一直保持,知道shutdownNow()方法
2. Executors.newSigleThreadPool
源码:
public staticExecutorService newSingleThreadExecutor() {
return newExecutors.FinalizableDelegatedExecutorService(newThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS, newLinkedBlockingQueue()));
}
顾名思义,SingleThreadPool内部维持一个核心线程,确保任务都在同一线程执行,不会出现并发操作。
3. Executors.newCacheThreadPool
源码:
public staticExecutorService newCachedThreadPool() {
return newThreadPoolExecutor(0,2147483647,60L,TimeUnit.SECONDS, newSynchronousQueue());
}
CacheThreadPool没有核心线程,最大线程池为Integer.MAX_VALUE(非常大。。。),当所有线程都执行任务时,会为新任务开启新线程,如果有空闲线程,则复用。而且线程空闲60s,会被销毁。
4. Executors.newSheduledThreadPool
源码:
public staticScheduledExecutorService newScheduledThreadPool(intvar0) {
return newScheduledThreadPoolExecutor(var0);
}
public ScheduledThreadPoolExecutor(intvar1) {
super(var1,2147483647,0L,TimeUnit.NANOSECONDS, newScheduledThreadPoolExecutor.DelayedWorkQueue());
}
ScheduledThreadPool支持任务延迟执行和周期重复执行,核心线程固定,非核心线程Integer.MAX_VALUE,如果非核心线程没有任务执行,会被立即销毁
三、总结
线程池顶级接口Executor,其子接口ExecutorService 和 ScheduledExcutorService初建线程池模型,由他们的子类ThreadPoolExecutor 和 ScheduledThreadPoolExecutor具体实现线程池功能,开发中可以根据需求配置不同功能的线程池。为了方便开发使用,java提供了Executors工具类,提供4种不同功能的线程池,并且推荐使用。
本次技术分享,主要是带大家了解线程池常用类之间的关系,方便日后更深入的学习线程池。
本文到此结束,有什么不足或错误的地方,希望能批评指正,谢谢!
致谢:这篇文章大部分是来自研究api,也有部分是参考别人的博文,在此表示感谢!!!