一、线程池原理
1、什么是线程池
线程池就是将线程进行池化,需要运行任务时从池中拿一个线程来执行,执行完毕,线程还放回池中。
2、为什么要用线程池?
1)降低资源消耗。通过重复利用已创建的线程来降低创建和销毁线程造成的消耗。
2)提高响应速度。省去了创建和销毁线程所需要的时间。
3)提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
3、创建线程池时各个参数的含义
1)corePoolSize:线程池中核心线程数。当提交一个任务时,线程池会创建一个新线程执行任务,直到当前线程数等于corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
2)maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,又有新提交的任务,则创建新的线程执行任务,直到当前线程数等于maximumPoolSize。
3)keepAliveTime:线程空闲时的存活时间。默认情况下,该参数只在线程数大于corePoolSize时才有用。
4)TimeUnit:keepAliveTime的时间单位。
5)workQueue:workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。
阻塞队列:
a、支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满。
b、支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变成非空。
BlockingQueue接口的几个公共方法:
插入方法:
boolean add(E):当队列满时,再往队列里插入元素,会抛出IllegalStateException异常。
boolean offer(E):返回添加结果。
void put(E):当阻塞队列满时,队列会一直阻塞调用者所在的线程,直到队列可用或者响应中断退出。
移除方法:
boolean remove(Object):当队列为空时,从队列里获取元素会抛出NoSuchElementException异常。
E poll():从队列取出一个元素返回,如果没有则返回null。
E take():当阻塞队列为空时,队列会一直阻塞调用者所在的线程,直到队列不为空。
常用的阻塞队列:
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表结构组成的有界双向阻塞队列。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue:使用优先级队列实现的无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列。
6)threadFactory:创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。
7)RejectedExecutionHandler:饱和策略。当阻塞队列满了且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务。线程池提供了4种策略:
a、AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;
b、CallerRunsPolicy:用调用者所在的线程来执行任务;
c、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
d、DiscardPolicy:直接丢弃任务,但不抛出异常。
也可以根据自己的需要实现RejectedExecutionHandler接口,自定义饱和策略。
4、线程池的工作原理图
5、合理配置线程池
要合理的配置线程池,就必须要先分析任务特性。可以从以下几个角度来分析:
任务的性质:CPU密集型、IO密集型和混合型任务;
任务的优先级:高、中、低;
任务的执行时间:长、中、短;
任务的依赖性:是否依赖其他系统资源。如数据库连接。
性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。
IO密集型任务的线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。
可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
二、AsyncTask工作原理
在Android中,通常将线程分为两种,一种是Main Thread,除了Main Thread之外的线程都被称之为Work Thread。当一个应用程序运行的时候,Android操作系统会给该应用程序启动一个线程,这个线程我们称之为Main Thread。Main Thread主要用来加载我们的UI界面,完成系统和用户之间的交互并将交互结果展示给用户,所以Main Thread又被称为UI Thread。
在Android多线程编程中,有两条原则需要遵守:
(1) 不能在UI Thread当中进行耗时操作。(不能阻塞UI Thread,防止出现ANR)
(2) 不能在UI Thread之外的线程中操纵我们的UI元素。(因为Android UI控件是线程不安全的)
1、AsyncTask的使用
AsyncTask是个abstract类,在使用的时候需要实现AsyncTask的具体实现类。一般我们会覆盖4个方法:
(1) onPreExecute():在执行耗时操作操作之前调用,一般会在这个方法执行显示loading动画的操作。该方法运行在主线程。
(2) doInBackground():核心方法,在该方法中执行耗时操作,是必须要实现的一个方法。在onPreExecute()方法执行完以后会立即执行这个方法。这个方法执行在子线程中。
(3) onProgressUpdate():在doInBackground()中调用publishProgress()时的回调方法,用于更新下载进度,运行在主线程中。
(4) onPostExecute():后台下载操作完成后调用,将loading动画隐藏并更新UI,运行在主线程中。
AsyncTask task=new AsyncTask() {
@Override
protected void onPreExecute() {
//显示loading动画,做一些准备工作
}
@Override
protected Object doInBackground(Object[] objects) {
//执行耗时操作
return null;
}
@Override
protected void onProgressUpdate(Object[] values) {
//更新进度条
}
@Override
protected void onPostExecute(Object o) {
//隐藏loading动画,更新UI
}
};
task.execute();
2、AsyncTask工作原理
分析AsyncTask工作原理主要是查看构造方法和execute方法。
1)构造方法
可以看到在构造方法里,进行了一些初始化的操作:实例化了一个Handler对象,创建了一个WorkerRunnable对象,看代码可知WorkerRunnable实现了Callable任务接口,所以mWorker实际上就是一个任务对象,传到了下边的FutureTask对象里。
在WorkRunnable的call()方法里调用了doInBackground()方法,这也就说明了为什么doInBackground()方法是在子线程中执行的。
FutureTask的done()方法是在Callable任务执行完以后调用的,可以看到任务执行完以后,就只是调用了postResultIfNotInvoked()方法,该方法又调用了postResult()方法。postResult()方法里才真正做了任务执行完以后的操作。
可以看到,将doInBackground()方法处理的结果放到了Message对象里。然后我们再看一下handleMessage()方法。
可以看到,在任务执行的过程中,会调用onProgressUpdate方法,在任务执行结束后,会执行finish方法,在finish方法中调用了onPostExecute方法。
2)execute方法
调用execute方法后真正执行的是executeOnExecutor方法,在executeOnExecutor先是调用了onPreExecute方法,然后是执行了exec.execute()操作。exec就是execute()方法中的sDefaultExecutor变量。
可以看到sDefaultExecutor变量其实就是一个SerialExecutor对象。看一下SerialExecutor的实现:内部声明了一个双端队列ArrayDeque类型的mTasks(双端队列中offer方法表示从队列尾插入,poll()表示从队列头获取元素)。 然后在SerialExecutor对象的execute()方法中执行的操作是:先将任务放到双端队列mTasks中。然后在scheduleNext()方法中从mTask队列中取出一个元素执行THREAD_POOL_EXECUTOR.execute(mActive)操作。
然后看一下THREAD_POOL_EXECUTOR是什么。
这个THREAD_POOL_EXECUTOR就是一个线程池对象,这个线程池对象被声明为一个静态变量。
总结:
1、在使用AsyncTask的时候,内部会创建两个线程池SerialExecutor和ThreadPoolExecutor,SerialExecutor负责将任务串行化,ThreadPoolExecutor是真正执行任务的地方,且无论有多少个AsyncTask实例,两个线程池都会只有一份。
2、在执行execute()方法的时候,会执行run()方法,run()方法执行完了会调用scheduleNext()不断的从双端队列中轮询获取下一个任务并继续放到一个子线程中执行,直到异步任务执行完毕。
3、每一个new出的AsyncTask只能执行一次execute()方法,多次运行将会报错。