学习Android(十二)Thread
简介
Android 应用的 UI 操作都在主线程(Main Thread)执行,若在该线程中执行耗时任务,会导致界面卡顿甚至 ANR(应用无响应)。因此,需要在子线程中执行耗时、阻塞或复杂计算等任务,并通过合适的通信机制将结果回传给主线程。Android 提供了最原始的 Thread
类,和基于 Thread
封装的 HandlerThread
、ExecutorService
(线程池)等工具,以简化异步编程和线程管理。下面分别介绍各部分的属性、方法及示例。
1. 线程概念与生命周期
Android 中每个应用都运行在独立进程内,进程至少包含一个主线程(UI 线程),可以通过创建更多 Thread
实例来并行执行任务。主线程负责界面绘制、事件分发等,子线程则执行耗时操作,避免阻塞主线程。良好的线程使用能显著提升应用性能,减少 UI 卡顿。良好的线程是使用能显著提升应用性能,减少UI卡顿。
线程的典型生命周期包括:
-
NEW:线程对象创建,但未调用
start()
; -
RUNNABLE: 调用
start()
后,线程进入可运行状态,由系统调度; -
BLOCKED、WAITING、TIMED_WAITING:线程被阻塞或等待;
-
TERMINATED:
run()
方法执行完毕或抛出未捕获异常后结束。
2. Thread
类的核心属性与方法
class Thread : Runnable {// 构造函数constructor()constructor(runnable: Runnable)constructor(name: String)constructor(runnable: Runnable, name: String)// 启动线程,调用 run()fun start()// 线程执行入口(可由子类重写)override fun run()// 等待该线程终止fun join()// 中断线程fun interrupt()// 检查中断状态fun isInterrupted(): Boolean// 获取线程名称fun getName(): String// 设置线程名称fun setName(name: String)// 获取线程优先级(1~10,默认5)fun getPriority(): Int// 设置线程优先级fun setPriority(priority: Int)// 获取线程 IDfun getId(): Long// 获取线程状态fun getState(): Thread.State
}
-
start()
与run()
:调用start()
后,JVM 创建新调用栈并最终执行run()
;直接调用run()
则在当前线程同步执行,不会新启线程。 -
join()
:让当前线程等待目标线程终止后再继续执行,可指定超时。 -
interrupt()
/isInterrupted()
:用于中断阻塞中或正在运行的线程。 -
线程名称、ID、优先级:名称便于调试,优先级(
Thread.MIN_PRIORITY=1
到Thread.MAX_PRIORITY=10
)影响调度,但 Android 上意义有限,默认优先级为 5。
3. HandlerThread
带消息循环的后台线程
HandlerThread
继承自 Thread
,内部自动创建一个 Looper
和消息队列,可以在该线程中创建 Handler
来处理消息或 Runable
。
-
构造方法
class HandlerThread(name: String, priority: Int = Process.THREAD_PRIORITY_DEFAULT)
-
关键方法
-
start()
:启动线程并准备Looper
; -
getLooper()
:返回线程内部的Looper
,在调用start()
后可获取; -
getThreadeHandler()
:可自行保存Handler
;
-
-
实现原理:
HandlerThread
重写了run()
,在启动后先为当前线程调用Looper.prepare()
,然后Looper.loop()
开始消息循环,最后退出循环时清理Looper
。 -
使用
HandlerThread()
示例// 创建并启动 val handlerThread = HandlerThread("WorkerThread").apply { start() } // 在该线程中创建 Handler val workerHandler = Handler(handlerThread.looper) workerHandler.post {// 后台执行耗时操作val result = doHeavyWork()// 切回主线程更新 UImainHandler.post { updateUI(result) } } // 停止循环并释放 handlerThread.quitSafely()
4. 线程池与 ExecutorService
直接创建 Thread
易导致线程数量时空且难管理,Java提供 Executor
框架用于统一管理线程池和任务调度。
interface Executor {void execute(Runnable command);
}interface ExecutorService extends Executor {Future<?> submit(Runnable task);<T> Future<T> submit(Callable<T> task);void shutdown();List<Runnable> shutdownNow();
}
-
Executors.newFixedThreadPool(n)
:固定大小线程池; -
newCachedThreadPool()
:根据需要创建新线程,空闲线程 60 秒后回收; -
newSingleThreadExecutor()
:单线程串行执行; -
newScheduledThreadPool(n)
:支持延迟与周期执行。
线程池属性
-
CorePoolSize:核心线程数;
-
MaximumPoolSize:最大线程数;
-
KeepAliveTime:非核心线程空闲存活时间;
-
WorkQueue:任务队列;
-
ThreadFactory:自定义线程创建方式;
-
RejectedExecutionHandler:线程池满时的拒绝策略
5. 异步通信:Handler、Message、Looper
Android 提供 Handler
、Message
、MessageQueue
、Looper
等机制,用于线程间信息传递。
-
Looper.prepare()
/Looper.loop()
:在子线程中初始化并启动消息循环; -
Handler
:绑定到某个Looper
,通过sendMessage()
、post()
将任务投递到其所在线程的消息队列; -
Message
:携带what
、arg1
、arg2
、obj
等字段用于传参; -
Handler.Callback
与handleMessage()
:处理消息;
子线程向主线程发送消息示例
// 在子线程中
val childHandler = Handler(Looper.getMainLooper()) // 绑定主线程
childHandler.post {// 更新 UItextView.text = "任务完成"
}// 在子线程自建 Looper
val thread = Thread {Looper.prepare()val handler = Handler { msg ->// 处理 msgtrue}Looper.loop()
}
thread.start()
6. 以下示例演示了如何用单独 Activity 界面来体验三种常见的线程使用方式
-
原生
Thread
ThreadActivity
class ThreadActivity: AppCompatActivity() {companion object {const val TAG = "ThreadDemo"}private lateinit var statusText: TextViewprivate lateinit var startButton: Buttonprivate lateinit var interruptButton: Buttonprivate var demoThread: Thread? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 创建简单UIval layout = LinearLayout(this).apply {orientation = LinearLayout.VERTICALsetPadding(40, 40, 40, 40)}statusText = TextView(this).apply {text = "准备开始线程演示"textSize = 18f}startButton = Button(this).apply {text = "启动线程"}interruptButton = Button(this).apply {text = "中断线程"}layout.addView(statusText)layout.addView(startButton)layout.addView(interruptButton)setContentView(layout)val mainHandler = Handler(Looper.getMainLooper())// 示例:使用 Runnable 和命名构造函数创建线程val myRunnable = Runnable {Log.d(TAG,"线程开始,名称: ${Thread.currentThread().name}, ID: ${Thread.currentThread().id}")for (i in 1..5) {if (Thread.currentThread().isInterrupted) {Log.d(TAG, "线程被中断,退出")break}Log.d(TAG, "线程运行中:$i")mainHandler.post {statusText.text = "线程运行中:$i"}try {Thread.sleep(1000)} catch (e: InterruptedException) {Log.d(TAG, "线程睡眠中被中断")Thread.currentThread().interrupt() // 重置中断标志break}}mainHandler.post {statusText.text = "线程执行完毕"}Log.d(TAG, "线程结束")}startButton.setOnClickListener {if (demoThread?.isAlive == true) {Toast.makeText(this, "线程已在运行", Toast.LENGTH_SHORT).show()return@setOnClickListener}// 使用 Thread(Runnable, name) 构造函数demoThread = Thread(myRunnable, "MyCustomThread").apply {priority = Thread.NORM_PRIORITY // 设置优先级(1-10)}demoThread?.start()}interruptButton.setOnClickListener {demoThread?.interrupt()statusText.text = "已请求中断线程"}} }5000).also {isMonitoring.set(false)Log.d(TAG, "停止监控")}} }
功能 示例代码 说明 构造函数 Thread(Runnable, name)
使用具名构造函数创建线程 start()
demoThread?.start()
启动线程 sleep()
Thread.sleep(1000)
模拟耗时任务 interrupt()
demoThread?.interrupt()
发送中断请求 isInterrupted()
Thread.currentThread().isInterrupted
检测中断状态 isAlive()
demoThread?.isAlive
判断线程是否仍在运行 priority
Thread.NORM_PRIORITY
设置线程优先级(默认是 5) -
HandlerThread
HandlerThreadActivity
class HandlerThreadActivity : AppCompatActivity() {companion object {const val TAG = "HandlerThreadDemo"}private lateinit var statusText: TextViewprivate lateinit var startButton: Buttonprivate lateinit var stopButton: Buttonprivate lateinit var handlerThread: HandlerThreadprivate lateinit var workHandler: Handleroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 简单布局val layout = LinearLayout(this).apply {orientation = LinearLayout.VERTICALsetPadding(40, 40, 40, 40)}statusText = TextView(this).apply {text = "HandlerThread 示例初始化"textSize = 18f}startButton = Button(this).apply { text = "开始任务" }stopButton = Button(this).apply { text = "停止线程" }layout.addView(statusText)layout.addView(startButton)layout.addView(stopButton)setContentView(layout)// 初始化 HandlerThreadhandlerThread = HandlerThread("MyHandlerThread", Process.THREAD_PRIORITY_BACKGROUND).apply {start() // 启动线程(必须)}// 创建工作 Handler(绑定到 handlerThread 的 Looper)workHandler = object : Handler(handlerThread.looper) {override fun handleMessage(msg: Message) {Log.d(TAG, "handleMessage(): what=${msg.what}, thread=${Thread.currentThread().name}")Thread.sleep(1000) // 模拟耗时runOnUiThread {statusText.text = "完成消息:${msg.what}"}}}startButton.setOnClickListener {if (!handlerThread.isAlive) {Toast.makeText(this, "线程已关闭,请重启 Activity", Toast.LENGTH_SHORT).show()return@setOnClickListener}for (i in 1..5) {workHandler.post {Log.d(TAG, "执行第 $i 个任务 on ${Thread.currentThread().name}")Thread.sleep(500)runOnUiThread {statusText.text = "任务 $i 完成"}}// 可发送 messageworkHandler.sendMessage(Message.obtain().apply { what = i + 100 })}}stopButton.setOnClickListener {Log.d(TAG, "准备退出 HandlerThread")handlerThread.quitSafely()statusText.text = "线程已请求退出"}}override fun onDestroy() {super.onDestroy()if (handlerThread.isAlive) {handlerThread.quitSafely()}} }
特性 表现 消息循环(Looper) handlerThread.looper
提供一个后台消息循环线程绑定 Handler 使用 Handler(handlerThread.looper)
把任务放到该线程任务处理 支持 post(Runnable)
和sendMessage(Message)
退出方式 使用 quitSafely()
可在处理完当前消息后安全退出-
运行结果:
-
点击「开始任务」后,会有 5 个任务与 5 个 message 被发送到
HandlerThread
线程处理 -
TextView
会显示每个任务的完成信息 -
点击「停止线程」后会安全退出
HandlerThread
-
-
-
Thread
和HandlerThread
对比项目 Thread
HandlerThread
类型 Java 基础线程类 Android 对 Thread 的增强 是否带消息队列 ❌ 没有 ✅ 内部有 Looper
和消息队列是否能重复接收任务 ❌ 通常一次性使用 ✅ 可以通过 Handler 多次发送任务 通常用途 简单的一次性耗时任务 需要持续处理任务或消息的后台线程 特性 Thread
HandlerThread
启动方式 thread.start()
handlerThread.start()
(再取 looper)任务提交方式 重写 run()
或传入Runnable
使用绑定的 Handler.post()
或Handler.sendMessage()
是否有消息队列 ❌ 无 ✅ 有(支持多任务队列) 是否有 Looper ❌ 无 ✅ 有(通过 handlerThread.looper
获取)生命周期 一次性执行任务后结束 持续运行,直到调用 quit()
或quitSafely()
可扩展性 灵活但手动管理多任务麻烦 内置消息机制,适合长时间后台工作 使用场景 简单短任务,快速执行 后台线程处理消息/任务(如日志、IO、图片压缩) Thread 做事快,用完就走;HandlerThread 持久干,排队处理不烦。
-
线程池
ExecutorService
ThreadPoolActivity
class ThreadPoolActivity : AppCompatActivity() {companion object {const val TAG = "ThreadPoolDemo"}private lateinit var executorService: ExecutorServiceprivate val futureTasks = mutableListOf<Future<*>>()private lateinit var startButton: Buttonprivate lateinit var cancelButton: Buttonprivate lateinit var statusText: TextViewprivate var taskCounter = 0override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// ========== 简单 UI ==========val layout = LinearLayout(this).apply {orientation = LinearLayout.VERTICALsetPadding(40, 40, 40, 40)}startButton = Button(this).apply { text = "提交任务" }cancelButton = Button(this).apply { text = "取消所有任务" }statusText = TextView(this).apply {text = "线程池未启动任务"textSize = 16f}layout.addView(startButton)layout.addView(cancelButton)layout.addView(statusText)setContentView(layout)// ========== 初始化线程池 ==========executorService = Executors.newFixedThreadPool(3)// ========== 启动任务 ==========startButton.setOnClickListener {submitMultipleTasks(5)}// ========== 取消任务 ==========cancelButton.setOnClickListener {for ((index, future) in futureTasks.withIndex()) {if (!future.isDone) {future.cancel(true)Log.d(TAG, "尝试取消任务 ${index + 1}")}}statusText.text = "已尝试取消所有任务"}}private fun submitMultipleTasks(count: Int) {for (i in 1..count) {val taskId = ++taskCounterval future = executorService.submit(Callable<String> {val threadName = Thread.currentThread().nameLog.d(TAG, "任务 $taskId 开始,线程:$threadName")try {repeat(5) { step ->if (Thread.currentThread().isInterrupted) {Log.d(TAG, "任务 $taskId 被中断")return@Callable "任务 $taskId 中断"}Log.d(TAG, "任务 $taskId 执行步骤 ${step + 1},线程:$threadName")Thread.sleep(500)}} catch (e: InterruptedException) {Log.d(TAG, "任务 $taskId 捕获中断异常")return@Callable "任务 $taskId 被异常中断"}Log.d(TAG, "任务 $taskId 完成,线程:$threadName")return@Callable "任务 $taskId 完成"})futureTasks.add(future)// 单独线程监控结果(避免主线程阻塞)Thread {try {val result = future.get()Log.d(TAG, "任务 $taskId 结果: $result")runOnUiThread {statusText.text = "任务 $taskId 完成"}} catch (e: Exception) {Log.d(TAG, "任务 $taskId 异常:${e.message}")}}.start()}}override fun onDestroy() {super.onDestroy()Log.d(TAG, "Activity 销毁,强制关闭线程池")executorService.shutdownNow()}}
ExecutorService 特点 展示方式 线程复用 FixedThreadPool(3)
,通过Thread.currentThread().name
可见并发处理多个任务 同时提交 5 个任务,线程池自动调度 取消任务 使用 Future.cancel(true)
中断获取结果 每个任务用 future.get()
单独监控主线程与子线程通信 用 runOnUiThread
更新状态到TextView
7. 线程相关面试题
-
如何创建一个线程?有哪几种方式?
-
继承
Thread
类:创建一个类继承Thread
,并重写其run()
方法。 -
实现
Runnable
接口:创建一个实现了Runnable
接口的类,并将其实例作为参数传递给Thread
类的构造函数。
这两种方式都可以创建并启动线程,但实现
Runnable
接口的方式更灵活,适用于多线程共享资源的场景。 -
-
start()
和run()
方法的区别?-
start()
方法:用于启动新线程,调用后 JVM 会创建新的线程并执行run()
方法。 -
run()
方法:包含线程要执行的任务代码。如果直接调用run()
方法,则不会创建新线程,而是在当前线程中执行。
-
-
线程的生命周期有哪些状态?
线程的生命周期包括以下几种状态:
-
新建(New):线程对象已创建,但尚未启动。
-
就绪(Runnable):线程已启动,等待被线程调度器调度执行。
-
运行(Running):线程正在执行。
-
阻塞(Blocked):线程被阻塞,等待获取锁。
-
等待(Waiting):线程进入等待状态,等待其他线程的通知。
-
计时等待(Timed Waiting):线程在指定时间内等待。
-
终止(Terminated):线程执行完毕或被中断。
-
-
如何实现线程间的通信?
可以使用
wait()
、notify()
和notifyAll()
方法实现线程间通信。这些方法必须在同步块或同步方法中调用,以确保线程安全。 -
如何实现线程的中断?
可以调用线程对象的
interrupt()
方法来中断线程。在线程的执行代码中,应定期检查isInterrupted()
状态,或捕获InterruptedException
异常,以响应中断请求。 -
什么是
HandlerThread
?HandlerThread
是 Android 中的一个类,它继承自Thread
,并自带一个Looper
。这使得我们可以在子线程中创建Handler
,从而处理消息队列,实现消息的异步处理。 -
HandlerThread
的使用场景有哪些?HandlerThread
适用于需要在后台线程中处理消息或执行耗时操作的场景,如:-
处理后台任务队列。
-
执行耗时的初始化操作。
-
避免在主线程中执行耗时操作导致的 ANR(应用无响应)。
-
-
如何停止
HandlerThread
?可以调用
quit()
或quitSafely()
方法来停止HandlerThread
:-
quit()
方法:立即停止Looper
,可能会导致消息队列中的消息未被处理完。 -
quitSafely()
方法:等待消息队列中的消息处理完毕后,再停止Looper
,更安全。
-
-
什么是
ExecutorService
?ExecutorService
是 Java 中的一个接口,提供了管理和控制线程池的功能。通过它可以实现任务的异步执行、线程的复用和资源的有效管理。 -
ExecutorService
的主要实现类有哪些?ExecutorService
的常用实现类包括:-
ThreadPoolExecutor
:最常用的线程池实现类,提供了灵活的线程池配置。 -
ScheduledThreadPoolExecutor
:支持任务的定时和周期性执行。
-
-
如何创建线程池?
可以使用
Executors
工具类提供的静态方法创建线程池,如:-
newFixedThreadPool(int nThreads)
:创建固定大小的线程池。 -
newCachedThreadPool()
:创建可根据需要创建新线程的线程池。 -
newSingleThreadExecutor()
:创建单线程的线程池。 -
newScheduledThreadPool(int corePoolSize)
:创建支持定时和周期性任务执行的线程池。
-
-
如何提交任务并获取执行结果?
可以使用
submit()
方法提交实现了Callable
接口的任务,该方法会返回一个Future
对象,通过Future.get()
方法可以获取任务的执行结果。 -
如何优雅地关闭线程池?
可以使用以下方法关闭线程池:
-
shutdown()
方法:启动线程池的有序关闭过程,已提交的任务会继续执行,但不再接受新任务。 -
shutdownNow()
方法:尝试停止所有正在执行的任务,并返回等待执行的任务列表。
-
-
如何确保线程不会造成内存泄漏?
-
避免线程持有长生命周期对象的强引用
-
及时停止不再需要的线程
-
清除未处理的消息和回调
-
正确使用线程池
-
使用弱引用管理线程中的资源
-
定期进行内存泄漏检测
-
-
如何保证线程执行的先后顺序
-
使用
Thread.join()
方法:join()
方法会使当前线程等待指定线程执行完毕后再继续执行,从而实现线程的顺序执行。 -
使用
CountDownLatch
:CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。 -
使用单线程线程池
Executors.newSingleThreadExecutor()
: 单线程线程池会按照任务提交的顺序依次执行各个任务,从而保证了线程的执行顺序。 -
使用
synchronized
、wait()
和notify()
: 通过对象锁和线程间的通信方法,可以精确控制线程的执行顺序。 -
使用
ReentrantLock
和Condition
:ReentrantLock
提供了比synchronized
更灵活的锁机制,配合Condition
可以实现更精细的线程控制。
-