线程在Android中是个很重要的概念,从用途来说,线程分为主线程与子线程,主线程用于处理界面相关事情,子线程用于执行耗时操作。除了Thread本身外,AysncTask,IntentService及HandleThread在安卓了都扮演着线程的角色。
AysncTask:底层封装了线程池和Handler,方便开发者在子线程中更新ui。
Handler:具有消息循环的线程,内部可以使用Handler。
IntentService:是一个服务,可执行后台任务,内部采用了HandlerThread来执行任务,任务执行完毕IntentService会自动退出。
一 JAVA传统启用线程的方法
1.继承Thread,实现run方法,通过.start方法启动并执行。
public class MyThread extends Thread {
//继承Thread类,并改写其run方法
public void run(){
Log.d(TAG, "run");
}
}
//启动线程并执行run方法
new MyThread().start();
2.实现Runnable接口,通过创建runnable对象来启动并执行。
public class MyRunnable implements Runnable{
@Override
public void run() {
Log.d(TAG, "run");
}
}
//启动并执行
new Thread(new MyRunnable()).start();
二 使用线程池
传统线程的缺陷
1.在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销毁每一个线程,所以会造成线程频繁地创建与销毁。
2.多个线程频繁地创建会占用大量的资源,并且在资源竞争的时候就容易出现问题,同时这么多的线程缺乏一个统一的管理,容易造成界面的卡顿。
3.多个线程频繁地销毁,会频繁地调用GC机制,这会使性能降低,又非常耗时。
使用线程池的优点
1.首先通过线程池中线程的重用,减少创建和销毁线程的性能开销。
2.能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。
3.线程池能够对线程进行管理,比如使用ScheduledThreadPool来设置延迟N秒后执行任务,并且每隔M秒循环执行一次
安卓封装了四种线程池的起源
安卓封装了四种线程池,FixedThreadPool、CachedThreadPool、SingleThreadExecutor、ScheduledTheadPool,这些线程池概念来源于Java中的Executor,它是一个接口,真正的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,不同的类型线程池只是不同程度的调整了ThreadPoolExecutor的参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
corePoolSize: 线程池中核心线程的数量。
maximumPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数小于maximumPoolSize时,则线程池仍然会创建新的线程来处理任务
keepAliveTime:非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长。
unit:keepAliveTime这个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等。
workQueue:线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory:为线程池提供创建新线程的功能,这个我们一般使用默认即可。
**handler: 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
线程池工作流程:
1.当currentSize<corePoolSize时,直接启动一个核心线程并执行任务。
2.当currentSize>=corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。若核心线程空闲,则将队列中的任务取出并交由核心线程执行。
3.当workQueue已满,但是currentSize<maximumPoolSize时,会立即开启一个非核心线程来执行任务。
4.当currentSize>=corePoolSize、workQueue已满、并且currentSize>maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。
maximumPoolSize(最大线程数) = corePoolSize + 最大非核心线程数
FixedThreadPool
public static ExecutorService newFixThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
//使用
Executors.newFixThreadPool(5).execute(runnable);
行为:FixThreadPool只有核心线程,并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。
特点:由于线程不会回收,FixThreadPool会更快地响应外界请求,这也很容易理解,就好像有人突然想上厕所,公厕不是现用现建的。
SingleThreadPool
public static ExecutorService newSingleThreadPool (int nThreads){
return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor (1, 1, 0, TimeUnit. MILLISECONDS, new LinkedBlockingQueue<Runnable>()) );
}
//使用
Executors.newSingleThreadPool ().execute(r);
行为:SingleThreadPool只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。
特点:所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。
CachedThreadPool
public static ExecutorService newCachedThreadPool(int nThreads){
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit. SECONDS, new SynchronousQueue<Runnable>());
}
//使用
Executors.newCachedThreadPool().execute(r);
行为:CachedThreadPool只有非核心线程,最大线程数非常大,所有线程都活动时,会为新任务创建新线程,否则利用空闲线程(60s空闲时间,过了就会被回收,所以线程池中有0个线程的可能)处理任务。
特点:任务队列SynchronousQueue相当于一个空集合,导致任何任务都会被立即执行
ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedQueue ());
}
//使用,延迟1秒执行,每隔2秒执行一次Runnable r
Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);
行为:核心线程数固定,非核心线程(闲着没活干会被立即回收)数没有限制。
特点:ScheduledThreadPool主要用于执行定时任务以及有固定周期的重复任务。
线程池中关于队列的疑问:
线程池为什么要用(阻塞)队列?
因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。
创建线程池的消耗较高。线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。
线程池为什么要使用阻塞队列而不使用非阻塞队列?
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。使得在线程不至于一直占用cpu资源。
线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下
while (task != null || (task = getTask()) != null) {})。
三 AsyncTask
AsyncTask定义:
一个Android 已封装好的轻量级异步类
属于抽象类,即使用时需 实现子类
作用:
实现多线程
在工作线程中执行任务,如 耗时任务
异步通信、消息传递
实现工作线程 & 主线程(UI线程)之间的通信
优点:
1.方便实现异步通信,不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合
2.节省资源采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销
四 Handler
handler主要用户线程间的通讯,故在此不做展开。
参考文章
android 线程池使用就是这么简单
Android开发——Android中常见的4种线程池
Android与线程池
Android 多线程:手把手教你使用AsyncTask