概述
Android程序中默认只有一个进程,一个进程里面可以包含多个线程。
多线程 - 介绍
定义:多个线程同时进行,即多个任务同时进行。
注意:
- 其实,计算机任何特定时刻只能执行一个任务;
- 多线程只是一种错觉:只是因为JVM快速调度资源来轮换线程不断轮流执行,所以看起来好像在同时执行多个任务而已。
一、Android中线程分类及作用
1.1 按用途分类
- 主线程:
定义:Android系统在程序启动时会自动启动一条主线程
作用:处理四大组件与用户进行交互的事情(如UI、界面交互相关)
注:因为用户随时会与界面发生交互,因此主线程任何时候都必须保持很高的响应速度,所以主线程不允许进行耗时操作, 否则会出现ANR
- 子线程:
定义:一个基本的CPU执行单元 & 程序执行流的最小单元。
作用:处理耗时操作,比如网络请求,复杂计算等,减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
子线程的状态如图所示:
----------------------------------------------------------Thread-------------------------------------------------------------
2.2 按形态分类:
一、Thread
1、说明: 基本的线程,可以做一些简单的操作,经常配合Handler使用。
2、Thread主要函数:
run():线程运行时所执行的代码
start():启动线程
sleep():线程休眠,进入阻塞状态,sleep方法不会释放锁
yield():线程交出CPU,但是不会阻塞而是重置为就绪状态,不会释放锁
interrupt():中断线程,注意只能中断阻塞状态的线程
setDaemon():设置和获取是否守护线程
3、Thread的几种状态
新建状态(new):实例化之后进入该状态;
就绪状态(Runnable):线程调用start()之后就绪状态等待cpu执行,注意这时只是表示可以运行并不代表已经运行;
运行状态(Running):线程获得cpu的执行,开始执行run()方法的代码;
阻塞状态(Blocked):线程由于各种原因进入阻塞状态:join()、sleep()、wait()、等待触发条件、等待由别的线程占用的锁;
死亡状态(Dead):线程运行完毕或异常退出,可使用isAlive()获取状态。
4、Android中Thread的使用
1.1、继承Thread,重写run()
方法。
1.2、实现Runnable,重写run()
方法来执行任务
1.3、通过Handler启动线程
5、如何终止线程
1.1、使用boolean变量作为标记
1.2、使用interrupt()
线程正常运行状态: 中断线程而不是结束线程。
线程阻塞状态:抛出异常,利用这个异常来跳出循环。
1.3、使用stop()
方法终止线程
这个相当于强制关机,万一数据丢失得不偿失。
6、线程安全与线程同步
1.1、什么是线程安全问题?
简单地说,线程安全问题是指多个线程访问同一代码或数据,造成结果和数据的错乱。
1.2、解决的方法:
(1)synchronized 关键字,保证同时刻只有一个线程进入该方法或者代码块。
(2)volatile特殊域变量修饰变量:告诉虚拟机该变量随时可能更新,因此使用时每次都会重新计算,而不是使用寄存器的值。
(3)ReentrantLock: 使用重入锁实现线程同步。
(4)ThreadLocal管理变量:每一个使用该变量的线程都会获得该变量的副本,副本之间相互独立。
---------------------------------------------------AsyncTask--------------------------------------------------------------
二、AsyncTask
1、说明:轻量级的异步操作类,方便更新UI。
2、原理:封装了Handler和两个线程池。
(1)线程池THREAD_POOL_EXECUTOR:正真执行线程的任务
(2)线程池SERIAL_EXECUTOR:任务调度(让多个线程任务 按顺序排列)
(3)AsyncTask创建时会实例化一个WorkerRunnable对象mWorker和一个FutureTask对象mFuture。FutureTask是一个并 发类,充当Runnable的作用。接着会串行排队并使用工作线程来处理实际任务。
(4)FutureTask对象创建时把WorkerRunnable对象作为参数传递,在FutureTask在执行任务run()
时会调用 WorkerRunnable的call()
方法,因此call()
方法是在线程池中进行的。
(5)主线程的Handler:
- 调用了call()方法后,调用doInBackground并返回结果。
- 这个过程中任务被取消会catch跳出设置AsyncTask结束。
call()
方法最后postResult(result)。
- 获取主线程的Handler,进行工作线程和主线程的通信
3、参数以及核心函数
AsyncTask还提供了4个核心方法:
protected void onPreExecute()
:在主线程执行,异步任务执行前调用做准备工作;protected abstract Result doInBackground(Params... var1)
:在线程池中执行,用于执行异步任务。Params表示异步任务输入参数,可以在此方法中通过publishProgress方法来更新任务进度,publishProgress方法又调用onProgressUpdate方法实现主线程进度更新。这个方法返回Result给onPostExecute方法。protected void onProgressUpdate(Progress... values)
:在主线程执行,后台任务执行进度发生变化会调用此方法。protected void onPostExecute(Result result)
:在主线程执行,异步任务执行后调用,result参数是由doInBackground返回的。
4、AsyncTask使用注意
- AsyncTask的对象、类必须在主线程创建,加载。
- execute方法必须在UI线程调用。
- 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则报运行时异常。
5、 优点
- 简单快捷,使用方便。
- UI更新及时,过程可控。
6、缺点
1、多个异步操作需要更新UI时,就变得麻烦起来。
---------------------------------------------------HandlerThread-----------------------------------------------------
三、HandlerThread
1、说明:一个使用了Looper、Handler的线程。
2、 原理
(1)继承了Thread,实际上是一个使用Looper、Handler的线程。
(2)继承了Thread,在run()
方法中通过Looper.prepare()
来创建消息队列,Looper.loop()
来循环处理消息。
(3)使用时开启HandlerThread,创建Handler与HandlerThread的Looper绑定,Handler以消息的方式通知HandlerThread来执行一个具体的任务。
(4)HandlerThread内部维护了一个消息队列,避免多次创建和销毁子线程来进行操作。
3、用例:
//创建HandlerThread实例,参数字符串定义新线程的名称。
HandlerThread mHandlerThread = new HandlerThread("check-message-coming");
//启动HandlerThread线程。
mHandlerThread.start();
//Handler与HandlerThread绑定
Handler mCheckMsgHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg){
// 进行耗时操作
}
};
注意:要记得在onPause()
和onDestroy()
中暂停更新和停止mHandlerThread以释放内存。
-----------------------------------------------------IntentService-----------------------------------------------------
四、IntentService
1、说明:IntentService封装了HandlerThread和一个Handle。
2、原理:
- IntentService创建时启动一个HandlerThread,同时将Handler绑定HandlerThread。所以通过Handler发送的消息都在HandlerThread中执行。
- 然后IntentService进入生命周期
onStartCommand
再调用onStart
将传进的Intent对象以消息的形式使用Handler发送。 - Handler收到消息后会调用
onHandleIntent
这样一个抽象方法,这个方法需要我们自己实现去处理逻辑。最后处理完毕stopSelf(msg.arg1);
等待所有任务完成结束IntentService;
-------------------------------------------------------线程池-------------------------------------------------------------
五、线程池
1、原理:
Android中的线程池概念来源于Java中的Executor,Executor是一个接口,真正的线程的实现为ThreadPoolExecutor。 (ThreadPoolExecutor继承了AbstractExecutorService,AbstractExecutorService是ExecutorService的实现类, ExecutorService继承了Executor接口)。
2、优点:
- 重用线程池中的线程,避免频繁创建和销毁线程所带来的内存开销。
- 有效控制线程的最大并发数,避免因线程之间抢占资源而导致的阻塞现象。
- 能够对线程进行简单的管理,提供定时执行以及指定时间间隔循环执行等功能。
3、线程池的分类
(1)、 FixedThreadPool (Fixed:固定的,不变的)
通过Executors的newFixedThreadPool
创建,通过创建时的参数可以看出又以下几个特点:
- 线程数量固定且都是核心线程:核心线程数量和最大线程数量都是nThreads;
- 都是核心线程且不会被回收,快速相应外界请求;
- 没有超时机制,任务队列也没有大小限制;
- 新任务使用核心线程处理,如果没有空闲的核心线程,则排队等待执行。
(2)、CachedThreadPool (Cached:缓存)
通过Executors的newCachedThreadPool
创建,特点:
- 线程数量不定,只有非核心线程,最大线程数任意大:传入核心线程数量的参数为0,最大线程数为Integer.MAX_VALUE;
- 有新任务时使用空闲线程执行,没有空闲线程则创建新的线程来处理。
- 该线程池的每个空闲线程都有超时机制,时常为60s(参数:60L, TimeUnit.SECONDS),空闲超过60s则回收空闲线程。
- 适合执行大量的耗时较少的任务,当所有线程闲置超过60s都会被停止,所以这时几乎不占用系统资源。
(3)、ScheduledThreadPool(Scheduled:预定的、排定的)
通过Executors的newScheduledThreadPool
创建,特点:
- 核心线程数量固定,非核心线程数量无限制;
- 非核心线程闲置超过10s会被回收;
- 主要用于执行定时任务和具有固定周期的重复任务;
- 四个里面唯一一个有延时执行和周期重复执行的功能
(4)、SingleThreadExecutor (单线程线程池)
通过Executors的newSingleThreadExecutor
创建,特点:
- 只有一个核心线程,所有任务在同一个线程按顺序执行。
- 所有的外界任务统一到一个线程中,所以不需要处理线程同步的问题。
4、ThreadPoolExecutor介绍
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
参数的介绍:
- corePoolSize:线程池的核心线程数,默认情况下核心线程会一直存活于线程池,即使处于闲置状态。
- maximumPoolSize:线程池所能容纳的最大线程数,当线程数达到这个数值后,后续的新任务将会被阻塞。
- keepAliveTime:非核心线程超时时长,超过这个时间没有任务执行,非核心线程就会被回收。
- unit:用于指定keepAliveTime参数的时间单位。
- workQueue:线程池中的任务队列。
- threadFactory:线程工厂,为线程池提供创建新线程的功能。
-
handler:该handler的类型为RejectedExecutionHandler。当线程池无法执行新任务时,会调用handler的
rejectedExecution(Runnable r, ThreadPoolExecutor e)
方法来抛出异常。
5、ThreadPoolExecutor执行任务大致遵循以下规则:
(1)如果线程池中的线程数量未达到核心线程的数量,会直接启动一个核心线程来执行任务。
(2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
(3)如果第2步中无法插入新任务,说明任务队列已满,如果未达到规定的最大线程数量,则启动一个非核心线程来执行任务。
(4)如果第3步中线程数量超过规定的最大值,则拒绝任务并使用RejectedExecutionHandler的rejectedExecution(Runnable r, ThreadPoolExecutor e)
方法来通知调用者。
原文链接:https://www.jianshu.com/p/56163a3beb4a
https://cloud.tencent.com/developer/article/1424838
最后
以上就是腼腆奇迹为你收集整理的Android 多线程面试的全部内容,希望文章能够帮你解决Android 多线程面试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复