Thread(线程)是处理器时间的基本单位,拥有自己的堆栈并独立于其他线程运行。在移动开发中,线程用于并行执行任务,以便界面在长时间操作期间保持响应。Android 支持 java.lang.Thread、Executors 和 Kotlin Coroutines,iOS 支持 Thread(Objective-C)、GCD 和 OperationQueue。根据 Android Thread Documentation,创建原生线程需要操作系统为堆栈分配约 1 MB 空间。
要点
Thread(执行线程)是操作系统可以在 CPU 内核上调度的一组独立指令序列。每个进程(应用程序)至少包含一个线程——主线程(Main Thread)。额外的线程用于并行执行任务。每个线程都有自己的程序堆栈(包含局部变量)、程序计数器(PC)和寄存器。堆内存由进程的所有线程共享。
在移动操作系统中,线程通过抢占式多任务处理(preemptive multitasking)进行调度:操作系统可以随时中断一个线程的执行并将控制权交给另一个线程(context switch)。上下文切换是一项代价高昂的操作(1–10 微秒),因为它需要保存/恢复 CPU 寄存器、更新 TLB 和清空缓存。正因为如此,过多的线程(成百上千个)会降低性能——操作系统在切换上花费的时间比执行还要多。
线程与进程是不同的概念。进程是拥有独立虚拟内存的应用程序实例。进程内的线程与其他线程共享这块内存。在 Android 中,应用程序的每个组件(Activity、Service、BroadcastReceiver)都在一个进程中工作,但可以在不同的线程中执行。iOS 应用程序也是一个进程,可以通过 GCD 或 Thread 创建额外线程。
每个线程在 Java/Kotlin(Android)和 NSThread(iOS)中都会经历五种状态:New(已创建)、Runnable(就绪)、Running(正在 CPU 上执行)、Blocked/Waiting(等待资源或通知)、Terminated(已终止)。状态之间的转换由操作系统调度器和同步原语管理。开发者可以影响线程的优先级(Thread.setPriority())及其状态(sleep、join、interrupt)。
在 Android 中,当线程尝试获取被占用的监视器(synchronized)、调用 Object.wait() 或 Thread.sleep() 时,会进入 Blocked 状态。在 iOS 中——调用 NSCondition.wait()、pthread_cond_wait() 或 dispatch_semaphore_wait() 时。在 Blocked 状态下,线程不消耗 CPU,但占用内存(堆栈)。线程可以从另一个线程被中断(interrupted),接收 InterruptedException(Java)或检查 isCancelled(Kotlin Coroutines)。
| 状态 | 描述 | 转换方法 |
|---|---|---|
| New | 线程已创建,但尚未启动 | Thread() 构造函数 |
| Runnable | 线程就绪,等待 CPU | thread.start() |
| Running | 线程正在 CPU 内核上执行 | 操作系统调度器 |
| Blocked/Waiting | 线程等待资源、监视器或通知 | synchronized、wait()、sleep() |
| Terminated | 线程已完成 run() 或已被中断 | run() 已完成、interrupt() |
Context switch(上下文切换)是操作系统保存当前线程状态(寄存器、PC、TLB)并加载另一个线程已保存状态的操作。在移动系统(Linux + ART,iOS 使用 XNU)中,上下文切换需要 1–10 微秒。如果线程用 100 微秒执行任务,而上下文切换需要 5 微秒,那么 5% 的时间就被浪费了。为了最小化上下文切换,iOS 使用带 work stealing 的 GCD,Android 使用带 fixedThreadCount 的线程池。
Android 从底层的 java.lang.Thread 发展到现代的协程。每一层抽象都以更少的开销提供更多能力。Thread 是基础类,但不建议直接创建它:新线程不受池管理,难以监控和取消。AsyncTask(从 API 30 起已弃用)是一个进步,但存在内存泄漏和配置处理不便的问题。
HandlerThread 是带有 Looper 的特殊 Thread 子类,可以处理消息队列。它用于在后台线程上顺序执行任务,例如将数据写入 Room 或文件。HandlerThread 通过调用 start() 创建,之后可以通过 Handler(handlerThread.looper) 发送消息和 Runnable。调用 handlerThread.quit() 会停止 Looper 并终止线程。
// Android:Thread、HandlerThread 和 Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. 直接创建 Thread(不推荐)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("直接线程已执行")
})
thread.start()
}
// 2. HandlerThread 用于顺序后台任务
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// 在后台线程上顺序执行
Thread.sleep(500)
print("HandlerThread:任务已完成")
}
// 停止线程(当任务完成时执行)
handlerThread.quitSafely()
}
// 3. Executors —— 线程池
fun executorExample() {
val executor = Executors.newFixedThreadPool(4)
for (i in 1..10) {
executor.execute {
print("Task $i on thread ${Thread.currentThread().getName()}")
}
}
executor.shutdown()
}
// 4. Kotlin Coroutines —— 现代标准
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
示例 ThreadExample 展示了 Android 中所有四种线程抽象层次。直接创建 Thread 是最底层且最低效的方法。HandlerThread 适合后台的顺序任务。Executors.newFixedThreadPool(4) 创建一个由 4 个线程组成的池,用于并行执行最多 10 个任务。使用 Dispatchers.Default 的 Kotlin Coroutines 是现代化、高效且安全的方式。
HandlerThread 是带有内置 Looper 和消息队列的 Thread 专用子类。通过调用 start() 创建,之后可以通过 Handler(handlerThread.looper) 发送 Runnable 和消息。HandlerThread 严格按顺序执行任务——下一个任务在前一个任务完成之前不会开始。这对于写入 Room 或文件(其中操作顺序至关重要)非常方便。调用 quitSafely() 会在当前任务完成后停止 Looper。
iOS 也提供了三种线程工作层次。Thread(Swift 中的 Thread,Objective-C 中的 NSThread)是直接创建原生线程的底层 API。通过 DispatchQueue 的 GCD(Grand Central Dispatch)是 iOS 开发者的主要工具,自动管理线程池。OperationQueue 是 GCD 之上的高层抽象,支持依赖、优先级和取消。
在现代 iOS 开发中直接使用 Thread 极其罕见——GCD 提供所有必要功能并自动管理内存和线程。Thread 仅用于特定情况:设置线程本地存储(threadDictionary)、为后台线程创建 RunLoop,或与期望 pthread_t 的 C 库集成。
import Foundation
class ThreadManager {
// 1. Thread(底层)
func createThread() {
let thread = Thread {
// 代码在新线程上执行
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD —— DispatchQueue
func gcdExample() {
// 并行队列
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD 异步任务")
}
// 用于写入同步的 Barrier
queue.async(flags: .barrier) {
// 写入期间的独占访问
print("Barrier write:独占访问")
}
}
// 3. 带依赖的 OperationQueue
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("正在下载...")
}
let process = BlockOperation {
print("正在处理...")
}
let save = BlockOperation {
print("正在保存...")
}
// 依赖:download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// 通过 GCD barrier 实现线程安全集合
class ThreadSafeArray<T> {
private var array: [T] = []
private let queue = DispatchQueue(label: "com.app.concurrent",
attributes: .concurrent)
var count: Int {
return queue.sync { array.count } // concurrent read
}
func append(_ element: T) {
queue.async(flags: .barrier) { // exclusive write
self.array.append(element)
}
}
}
ThreadSafeArray 类通过 GCD barrier 演示了 Concurrent Read / Exclusive Write 模式。通过 queue.sync{} 的读取从多个线程并行执行。通过 queue.async(flags: .barrier) 的写入会阻塞所有其他操作(包括读取和写入),直到写入完成。这比 synchronized 块更高效,因为在没有写入时不会阻塞读者。
在 iOS 中直接使用 Thread 在三种情况下是合理的:用于线程本地存储(Thread.current.threadDictionary)——存储与线程绑定的数据;通过 performSelector:onThread: 在后台线程创建专用 RunLoop;与期望 pthread_t 的 C/C++ 库集成。在所有其他情况下,通过 DispatchQueue 的 GCD 更可取——它能自动管理线程池和功耗。
Race condition(竞态条件)在两个或多个线程同时访问共享数据且至少其中一个执行写入时产生。结果取决于执行顺序(timing),且不可预测。为防止竞态条件,使用同步原语。在移动开发中可以使用锁(synchronized、NSLock)、原子操作(AtomicInteger、iOS 的 atomic 属性)和队列(serial queue)。
原语的选择取决于场景。对于简单的计数器和标志位,原子操作(AtomicInteger、atomic property)就足够了。对于包含多个操作的关键代码段——锁(synchronized、NSLock)。对于复杂的数据结构——serial DispatchQueue 或 GCD barrier。锁更容易理解,但容易产生死锁和活锁。队列更复杂,但更安全。
// Android/Kotlin 中的同步
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger —— 用于简单计数器
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized —— 用于关键代码段
@Synchronized
fun synchronizedOperation() {
// 每次只有一个线程
doWork()
}
// 3. 来自协程的 Mutex —— suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// protected code —— 线程安全
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// 死锁示例:A 阻塞 B,B 阻塞 A
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun methodA() = synchronized(lockA) {
Thread.sleep(100)
synchronized(lockB) { print("OK") }
}
fun methodB() = synchronized(lockB) {
Thread.sleep(100)
synchronized(lockA) { print("OK") }
}
}
Counter 演示了三种同步方法。AtomicInteger.incrementAndGet() 是一种无锁的原子操作(CAS)。@Synchronized 是 Java 内置的监视器,会锁定整个对象。Mutex.withLock 是协程互斥锁,它挂起协程而不是阻塞线程(更高效)。DeadlockExample 展示了经典死锁:两个线程以不同的顺序获取锁。
Thread Pool(线程池)是一组预先创建并重复使用的线程。池不是为每个任务创建新线程(成本高昂),而是从池中取出一个空闲线程。如果没有空闲线程,任务将进入队列。池会自动管理大小:在高负载时创建新线程,空闲线程会被终止。这使创建线程的开销降低了数十倍。
在 Android 中,Executors.newFixedThreadPool(4) 创建由 4 个线程组成的池。如果同时有 10 个任务到来,4 个会立即开始,6 个将在队列中等待。Executors.newCachedThreadPool() 按需(无限制)创建线程,并在 60 秒后终止空闲线程。对于 iOS,GCD 自动提供全局队列池,其大小与 CPU 内核数量和当前负载相匹配。
在 Kotlin Coroutines 中,线程池隐藏在调度器内部。Dispatchers.Default 使用大小等于 CPU 内核数量(至少 2)的池。Dispatchers.IO 使用 64 个线程(足够处理数百个 IO 密集型任务,因为大多数任务会在不占用 CPU 的情况下等待输入输出)。每个调度器都会根据负载自动扩展池,在空闲时节省电池电量。
常见问题
Thread 是应用程序中代码执行的基本单位。每个进程可以有多个线程,它们共享内存但拥有自己的堆栈。在移动开发中,线程用于并行执行任务而不阻塞 UI。Android 使用 Thread、Executors、HandlerThread 和 Coroutines。iOS 使用 Thread、GCD(DispatchQueue)和 OperationQueue。
创建 Thread 需要在 Android 中分配约 1 MB、在 iOS 中分配约 512 KB 的堆栈空间——这是代价高昂的操作。对于 1000 个任务,直接创建 1000 个线程将需要约 1 GB 仅用于堆栈,再加上上下文切换的开销。不要使用 Thread,而应使用线程池(Executors、GCD)或协程——它们会复用线程,将开销降低数十倍。
Race condition 是多个线程并发访问带写入的共享数据时出现的不可预测行为。可以通过三种方式避免:使用原子类型(AtomicInteger)、锁(synchronized、NSLock),或通过队列(serial DispatchQueue、Kotlin 中的 Actor)序列化访问。最佳实践是尽量减少共享的可变状态并使用不可变性(immutability)。
Thread 是原生系统对象,占用约 1 MB 堆栈并绑定到操作系统内核。协程是 Kotlin 中的轻量级执行单元,不绑定到特定线程,可以在不阻塞的情况下挂起(suspend)。一个线程可以执行数千个协程。协程在内存方面更高效,并允许编写无需回调的异步代码。
Deadlock 表现为应用程序完全卡住但没有 ANR。在 Android 中使用 Thread.getAllStackTraces() 转储所有线程的堆栈——两个线程将互相等待对方的锁。在 iOS 中——Thread.callStackSymbols。工具:Android Studio Profiler(Threads 标签页)、Instruments(iOS,Thread State View)。预防:按固定顺序获取锁,使用带超时的 tryLock。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。