前面讲了很多多线程的概念和创建原理,但是在Android中如何实现多线程,有几种实现多线程的方式?他们各自的实现原理是什么?以及他们之间的区别是什么?
一、多线程之线程池
线程池可以看成是一个线程的集合,需要运行任务时从池中拿一个线程来执行,执行完毕,线程放回池中。它内部又分为核心线程和非核心线程。
在开发过程中,合理地使用线程池能够带来3个好处:
(1):降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2):提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
(3):提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
一个线程池的六个核心成员组成:
(1)corepoolSize:核心线程数,线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
(2)maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize
(3)keepaliveTime:非核心线程空闲时的存活时间,即当非核心线程没有任务执行时,继续存活的时间。
(4)unit:超时时间的时间单位
(5)threadFactory:用于创建线程的工厂
(6)workqueue:任务阻塞队列(内部存放的是Runnable对象)
(7)RejectedExecutionException:线程池拒绝执行任务的处理策略。至于线程池什么时候会拒绝执行任务,请看下面的线程池工作流程。
针对RejectedExecutionException这里要补充一下,JDK为我们提供了四种拒绝执行任务的处理策略,他们分别是:
AbortPolicy:当任务数超过核心线程数以及最大线程数的时候,会直接抛出异常,Android自定义的四种线程池缺省策略就是它啦。
DiscardOldestPolicy:丢弃阻塞队列里面最靠前的任务,也就是处于对头的任务会被抛弃,将刚刚拒绝执行的任务重新插入阻塞队列。
DiscardPolicy:直接丢弃任务,什么也不做。
CallerRunsPolicy:将拒绝执行的任务交给任务提交者所在的线程取执行。
当然我们也可以自定义拒绝执行任务的策略,继承RejectedExecutionException就好了。
上面一直在说阻塞队列,但什么是阻塞队列呢。所以同时还要补充一下什么是阻塞队列
队列:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头(就像我们平时排队买票一样的)。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
阻塞队列:
1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
•抛出异常:当队列满时,如果再往队列里插入元素,会抛出IllegalStateException("Queuefull")异常。当队列空时,从队列里获取元素会抛出NoSuchElementException异常。
•返回特殊值:当往队列插入元素时,会返回元素是否插入成功,成功返回true。如果是移除方法,则是从队列里取出一个元素,如果没有则返回null。
•一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。
•超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。
常用阻塞队列
•ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
•LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
•PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
•DelayQueue:一个使用优先级队列实现的无界阻塞队列。
•SynchronousQueue:一个不存储元素的阻塞队列。
•LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
•LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
每个线程池的内部工作流程如下:
(1)首先判断是否有闲置的核心线程(即当前线程数小于corepoolSize),若有则启动一个核心线程去执行任务,若无进行步骤2
(2)若无闲置的核心线程,则将任务插入任务队列中排队。
(3)若插入任务队列失败(即队列已满),但线程数未超过最大值,则创建一个非核心线程执行该任务
(4)若当前线程数已达到最大线程值,则拒绝执行该任务,此时会抛出异常(执行RejectedExecutionException的rejectedExecution来通知调用者)。
Android中的线程池分类:
(1)、FixedThreadPool:只有核心线程的线程池,且数量固定,不会被随意回收,所执行的任务队列大小无限制。
(2)、CacheThreadPool:只有非核心线程,且数量不固定,最大支持Integer.MAX_VALUE个线程。且每个线程超时60秒闲置就会被回收,所有任务都是立即分配线程执行,不会进入等待队列。缺点是可无限创建,容易造成堆外内存溢出。
(3)、ScheduledThreadPool:核心线程数固定,非核心线程无限制。
(4)、SingleThreadPool:只有一个核心线程,保证任务都是在一个线程中顺序执行
Android中四种线程池的创建方式:
//只有一个核心线程数的线程池
private ExecutorService singleThreadPool1 = Executors.newSingleThreadExecutor();
private ExecutorService singleThreadPool2 = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
//创建只有核心线程的线程池,参数为核心线程数
ExecutorService fixedThreadPool1 = Executors.newFixedThreadPool(1);
ExecutorService fixedThreadPool2 = Executors.newFixedThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
//创建ScheduledThreadPool,参数为指定核心线程数
ExecutorService scheduledThreadPool1 = Executors.newScheduledThreadPool(2);
ExecutorService scheduledThreadPool2 = Executors.newScheduledThreadPool(2, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
//创建CacheThreadPool,该类型线程池只有非线程数,且无限制
ExecutorService cacheThreadPool1 = Executors.newCachedThreadPool();
ExecutorService cacheThreadPool2 = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
合理配置线程池
要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
•任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
•任务的优先级:高、中和低。
•任务的执行时间:长、中和短。
•任务的依赖性:是否依赖其他系统资源,如数据库连接。
性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务(通常不需要读取磁盘IO、网络IO的基本可以归结为CPU密集型)应配置尽可能小的线程,如配置CPU个数(Ncpu)+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数(Ncpu)。
二、多线程之AsyncTask
AsyncTask是Android已经封装好的轻量级异步类,且属于抽象类(即使用时需要实现其子类)。能够帮助我们实现工作线程与主线程(UI线程)之间的通信。即:将在工作线程中执行完耗时任务后将结果传递给主线程,保证类线程安全。
AsyncTask的工作原理 = Handler+任务队列线程池+任务执行线程池。
其中:任务队列线程池负责管理任务队列,任务执行线程池负责执行任务,Handler 用于异步通信
先来看看整个类的定义
public abstract class AsyncTask<Params, Progress, Result> {
...
}
// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
// a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
// b. Progress:异步任务执行过程中,返回下载进度值的类型
// c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
// a. 使用时并不是所有类型都被使用
// b. 若无被使用,可用java.lang.Void类型代替
// c. 若有不同业务,需额外再写1个AsyncTask的子类
}
下面我们将结合源码来分析一下AsyncTask的使用和原理
(1)步骤一:创建AsyncTask子类
/**
* 创建时间:2019/6/12
* 创建人:singleCode
* 功能描述:继承AsyncTask,创建子类
**/
public class MyAsyncTask extends AsyncTask<Void,Integer,String> {
// 作用:执行 线程任务前的操作,运行在主线程中
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果,运行在任务线程中
@Override
protected String doInBackground(Void... voids) {
//通知进度更新,之后会执行onProgressUpdate方法
publishProgress(100);
return null;
}
//接收进度更新,运行在主线程中,可直接更新UI
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
// 作用:接收线程任务执行结果、将执行结果显示到UI组件,运行在主线程中
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
//接收任务取消通知
@Override
protected void onCancelled() {
super.onCancelled();
}
}
(2)、步骤二:创建子类实例对象
MyAsyncTask asyncTask = new MyAsyncTask();
/**
*结合AsyncTask构造函数,分析源码
*/
public AsyncTask() {
// 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象 ->>分析1
mWorker = new WorkerRunnable<Params, Result>() {
// 在任务执行线程池中回调:THREAD_POOL_EXECUTOR.execute()
// 下面会详细讲解
public Result call() throws Exception {
// 添加线程的调用标识
mTaskInvoked.set(true);
Result result = null;
try {
// 设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 执行异步操作 = 耗时操作
// 即 我们使用过程中复写的耗时任务
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);// 若运行异常,设置取消的标志
throw tr;
} finally {
// 把异步操作执行的结果发送到主线程
// 从而更新UI,下面会详细讲解
postResult(result);
}
return result;
}
};
// 2. 初始化FutureTask变量 = 1个FutureTask ->>分析2
mFuture = new FutureTask<Result>(mWorker) {
// done()简介:FutureTask内的Callable执行完后的调用方法
// 作用:复查任务的调用、将未被调用的任务的结果通过InternalHandler传递到UI线程
@Override
protected void done() {
try {
// 在执行完任务后检查,将没被调用的Result也一并发出 ->>分析3
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
//若 发生异常,则将发出null
postResultIfNotInvoked(null);
}
}
};
}
/**
* 分析1:WorkerRunnable类的构造函数
*/
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
// 此处的Callable也是任务;
// 与Runnable的区别:Callable<T>存在返回值 = 其泛型
Params[] mParams;
}
/**
* 分析2:FutureTask类的构造函数
* 定义:1个包装任务的包装类
* 注:内部包含Callable<T> 、增加了一些状态标识 & 操作Callable<T>的接口
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
// 回到调用原处
/**
* 分析3:postResultIfNotInvoked()
*/
private void postResultIfNotInvoked()(Result result) {
// 取得任务标记
final boolean wasTaskInvoked = mTaskInvoked.get();
// 若任务无被执行,将未被调用的任务的结果通过InternalHandler传递到UI线程
if (!wasTaskInvoked) {
postResult(result);
}
}
(3)、步骤三:手动调用execute()
asyncTask.execute();
/**
* 源码分析:AsyncTask的execute()
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
// ->>分析1
}
/**
* 分析1:executeOnExecutor(sDefaultExecutor, params)
* 参数说明:sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
*/
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {
// 1. 判断 AsyncTask 当前的执行状态
// PENDING = 初始化状态
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
// 2. 将AsyncTask状态设置为RUNNING状态
mStatus = Status.RUNNING;
// 3. 主线程初始化工作
onPreExecute();
// 4. 添加参数到任务中
mWorker.mParams = params;
// 5. 执行任务
// 此处的exec = sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
// ->>分析2
exec.execute(mFuture);
return this;
}
/**
* 分析2:exec.execute(mFuture)
* 说明:属于任务队列 线程池类(SerialExecutor)的方法
*/
private static class SerialExecutor implements Executor {
// SerialExecutor = 静态内部类
// 即 是所有实例化的AsyncTask对象公有的
// SerialExecutor 内部维持了1个双向队列;
// 容量根据元素数量调节
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
// execute()被同步锁synchronized修饰
// 即说明:通过锁使得该队列保证AsyncTask中的任务是串行执行的
// 即 多个任务需1个个加到该队列中;然后 执行完队列头部的再执行下一个,以此类推
public synchronized void execute(final Runnable r) {
// 将实例化后的FutureTask类 的实例对象传入
// 即相当于:向队列中加入一个新的任务
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();->>分析3
}
}
});
// 若当前无任务执行,则去队列中取出1个执行
if (mActive == null) {
scheduleNext();
}
}
// 分析3
protected synchronized void scheduleNext() {
// 1. 取出队列头部任务
if ((mActive = mTasks.poll()) != null) {
// 2. 执行取出的队列头部任务
// 即 调用执行任务线程池类(THREAD_POOL_EXECUTOR)->>继续往下看
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
通过源码分析可以总结出如下几点:
(1)、执行任务前,通过任务队列线程池(SerialExecutor)将任务顺序加入任务队列中,并通过同步锁修饰execute()方法达到任务串行执行。
(2)、通过判断当前任务执行状态来决定是否执行下一个任务,将待执行的任务交给任务执行线程池。任务线程池类(THREAD_POOL_EXECUTOR)实际上是1个已配置好的可执行并行任务的线程池,调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务,而该耗时任务则是步骤2中初始化 WorkerRunnable实例对象时复写的call()内容。在call()方法里,先调用 我们复写的doInBackground(mParams)执行耗时操作,再调用postResult(result), 通过 InternalHandler 类 将任务消息传递到主线程;根据消息标识(MESSAGE_POST_RESULT)判断,最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作
三、多线程之HandlerThread
Android中Handler常作为线程间通信的一个重要组成部分,很多时候我们需要Handler创建在子线程中工作。一般情况下我们需要写出如下代码:
new Thread(new Runnable() {
@Override
public void run() {
// 在当前线程创建Looper对象
Looper.prepare();
//在当前线程中创建Handler并绑定当前线程的Looper
Handler handler =new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//处理业务逻辑
}
};
//开启消息循环队列
Looper.loop();
}
}).start();
有人会说你这不是脱裤子那啥吗?明明能直接在run方法里面执行任务了,还要Handler干嘛?这就要讲一讲什么是线程间通信,为什么要线程间通信了。在开发中很多时候我们处理的耗时任务都不止一个,且是不定时的。比如最简单的文件传输,客户端收到的数据是一段一段的。这时候我们就需要每次接收到数据时在另一个线程里解析并保存这些数据到本地(有人问为什么不能直接在接收数据的线程里直接解析非要去另外一个线程呢?这就是多线程编程的魅力所在了。如果我们把所有任务都放到一个线程中去执行,那这个线程的任务执行周期会非常长,一直被阻塞,严重影响任务执行的效率)。这时候重启一个新的线程,将解析任务交给另外一个线程去做,会极大的提高工作效率。但是线程与线程之间是不能够直接进行通信的(至于为什么不能,我们会在下一章的内存模型中详细讲解),这时候Handler这个桥梁就帮我解决了这个问题,每个handler对应一个Thread。我们只需要通过Handler对象就能够将要执行的线程交给Handler所在的线程去执行。但是如果每次都要这样写一堆代码是不是很烦呢。所以贴心的Android帮我们封装了一个HandlerThread,顾名思义HandlerThread是一个继承自Thread的线程,内部封装了Handler。那么如何使用呢?
HandlerThread handlerThread = new HandlerThread("test");
handlerThread.start();
//创建一个与HandlerThread绑定的Handler用来接收并处理任务
Handler workHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//TODO 接收并执行耗时任务
}
};
是不是简洁了很多,其实在Android源码中对HandlerThread的使用也非常常见,就比如我们经常用到的IntentService,我们先来观摩观摩IntentService源码
//继承自Service,具有Service的特性和生命周期
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//执行耗时任务
onHandleIntent((Intent)msg.obj);
//任务执行完后,直接结束自身,这也是它与其他Service不同的地方
stopSelf(msg.arg1);
}
}
public IntentService(String name) {
super();
mName = name;
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
//重点来了,这里直接创建了一个HandlerThread和Handler
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
//由外部使用者去实现任务的处理逻辑
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}