Thread — đơn vị cơ bản của thời gian xử lý CPU, có ngăn xếp riêng và thực thi độc lập với các luồng khác. Trong phát triển di động, các luồng được sử dụng để thực thi song song các tác vụ, giúp giao diện luôn phản hồi trong các thao tác kéo dài. Android hỗ trợ java.lang.Thread, Executors và Kotlin Coroutines, iOS — Thread (Objective-C), GCD và OperationQueue. Theo Tài liệu Thread Android, việc tạo một luồng gốc yêu cầu cấp phát ~1 MB cho ngăn xếp bởi hệ điều hành.
Chính yếu
Thread (luồng thực thi) — là một chuỗi lệnh độc lập mà hệ điều hành có thể lập lịch trên một lõi CPU. Mỗi tiến trình (ứng dụng) chứa ít nhất một luồng — Main Thread. Các luồng bổ sung được tạo ra để thực thi song song các tác vụ. Mỗi luồng có ngăn xếp phần mềm riêng (với biến cục bộ), bộ đếm lệnh (PC) và thanh ghi. Bộ nhớ heap được chia sẻ cho tất cả các luồng của tiến trình.
Trong hệ điều hành di động, các luồng được lập lịch bằng đa nhiệm ưu tiên (preemptive multitasking): HĐH có thể ngắt luồng bất kỳ lúc nào và chuyển điều khiển cho luồng khác (context switch). Chuyển đổi ngữ cảnh là thao tác tốn kém (1-10 micro giây), vì nó yêu cầu lưu/phục hồi thanh ghi CPU, cập nhật TLB và xóa bộ nhớ đệm. Đó là lý do tại sao số lượng luồng quá nhiều (hàng trăm, hàng nghìn) làm giảm hiệu suất — HĐH dành nhiều thời gian cho chuyển đổi hơn là thực thi.
Luồng và tiến trình — là các khái niệm khác nhau. Tiến trình là một phiên bản ứng dụng với bộ nhớ ảo được cấp phát riêng. Luồng bên trong tiến trình chia sẻ bộ nhớ này với các luồng khác. Trong Android, mỗi thành phần ứng dụng (Activity, Service, BroadcastReceiver) hoạt động trong một tiến trình, nhưng có thể thực thi trên các luồng khác nhau. Ứng dụng iOS cũng là một tiến trình duy nhất với khả năng tạo các luồng bổ sung qua GCD hoặc Thread.
Mỗi luồng trong Java/Kotlin (Android) và NSThread (iOS) trải qua năm trạng thái: New (được tạo), Runnable (sẵn sàng thực thi), Running (đang chạy trên CPU), Blocked/Waiting (chờ tài nguyên hoặc thông báo), Terminated (kết thúc). Các chuyển đổi giữa các trạng thái được quản lý bởi bộ lập lịch HĐH và các nguyên hàm đồng bộ hóa. Nhà phát triển có thể ảnh hưởng đến mức ưu tiên của luồng (Thread.setPriority()) và trạng thái của nó (sleep, join, interrupt).
Trong Android, một luồng chuyển sang trạng thái Blocked khi cố gắng chiếm monitor đang bận (synchronized), gọi Object.wait() hoặc Thread.sleep(). Trong iOS — khi gọi NSCondition.wait(), pthread_cond_wait() hoặc dispatch_semaphore_wait(). Ở trạng thái Blocked, luồng không tiêu thụ CPU nhưng chiếm bộ nhớ (ngăn xếp). Một luồng có thể bị ngắt (interrupted) từ luồng khác, nhận InterruptedException (Java) hoặc kiểm tra isCancelled (Kotlin Coroutines).
| Trạng thái | Mô tả | Phương thức chuyển đổi |
|---|---|---|
| New | Luồng được tạo nhưng chưa chạy | Hàm tạo Thread() |
| Runnable | Luồng sẵn sàng thực thi, chờ CPU | thread.start() |
| Running | Luồng đang chạy trên lõi CPU | Bộ lập lịch HĐH |
| Blocked/Waiting | Luồng chờ tài nguyên, monitor hoặc thông báo | synchronized, wait(), sleep() |
| Terminated | Luồng đã kết thúc run() hoặc bị ngắt | run() kết thúc, interrupt() |
Context switch (chuyển đổi ngữ cảnh) — thao tác HĐH lưu trạng thái của luồng hiện tại (thanh ghi, PC, TLB) và tải trạng thái đã lưu của luồng khác. Trong hệ thống di động (Linux + ART, XNU cho iOS), context switch mất 1-10 micro giây. Nếu một luồng thực thi tác vụ trong 100 micro giây và context switch mất 5, thì 5% thời gian bị lãng phí. Để giảm thiểu context switch, iOS sử dụng GCD với work stealing, Android — các pool với fixedThreadCount.
Android đã tiến hóa từ java.lang.Thread cấp thấp đến coroutines hiện đại. Mỗi mức trừu tượng mang lại nhiều khả năng hơn với chi phí thấp hơn. Thread là lớp cơ bản, nhưng việc tạo trực tiếp không được khuyến nghị: luồng mới không được quản lý bởi pool, khó giám sát và hủy bỏ. AsyncTask (không dùng nữa từ API 30) là một bước tiến, nhưng bị rò rỉ bộ nhớ và xử lý cấu hình bất tiện.
HandlerThread là một lớp con đặc biệt của Thread với Looper, có thể xử lý hàng đợi tin nhắn. Nó được sử dụng để thực thi tuần tự các tác vụ trên luồng nền, ví dụ ghi dữ liệu vào Room hoặc tệp tin. HandlerThread được tạo bằng cách gọi start(), sau đó qua Handler(handlerThread.looper) có thể gửi tin nhắn và Runnable. Gọi handlerThread.quit() dừng Looper và kết thúc luồng.
// Android: Thread, HandlerThread và Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. Tạo Thread trực tiếp (không khuyến nghị)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("Direct thread executed")
})
thread.start()
}
// 2. HandlerThread cho tác vụ nền tuần tự
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Thực thi tuần tự trên luồng nền
Thread.sleep(500)
print("HandlerThread: tác vụ hoàn thành")
}
// Dừng luồng (thực thi khi tác vụ hoàn tất)
handlerThread.quitSafely()
}
// 3. Executors — pool luồng
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 — tiêu chuẩn hiện đại
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
Ví dụ ThreadExample cho thấy cả bốn mức trừu tượng luồng trong Android. Tạo Thread trực tiếp là cách tiếp cận cấp thấp nhất và kém hiệu quả nhất. HandlerThread hữu ích cho các tác vụ tuần tự nền. Executors.newFixedThreadPool(4) tạo pool 4 luồng để thực thi song song tối đa 10 tác vụ. Kotlin Coroutines với Dispatchers.Default — cách hiện đại, hiệu quả và an toàn.
HandlerThread — lớp con chuyên biệt của Thread với Looper tích hợp và hàng đợi tin nhắn. Nó được tạo bằng cách gọi start(), sau đó qua Handler(handlerThread.looper) có thể gửi Runnable và tin nhắn. HandlerThread thực thi các tác vụ theo đúng thứ tự tuần tự — tác vụ tiếp theo không bắt đầu cho đến khi tác vụ trước kết thúc. Điều này thuận tiện cho việc ghi dữ liệu vào Room hoặc tệp tin, nơi thứ tự thao tác rất quan trọng. Gọi quitSafely() dừng Looper sau khi hoàn thành tác vụ hiện tại.
iOS cũng cung cấp ba mức làm việc với luồng. Thread (Thread trong Swift, NSThread trong Objective-C) — API cấp thấp, tạo trực tiếp luồng gốc. GCD (Grand Central Dispatch) qua DispatchQueue — công cụ chính cho nhà phát triển iOS, tự động quản lý pool luồng. OperationQueue — trừu tượng cấp cao trên GCD với hỗ trợ phụ thuộc, ưu tiên và hủy bỏ.
Việc sử dụng trực tiếp Thread trong phát triển iOS hiện đại cực kỳ hiếm — GCD cung cấp tất cả khả năng cần thiết với quản lý bộ nhớ và luồng tự động. Thread chỉ được sử dụng cho các trường hợp đặc biệt: thiết lập thread-local storage (threadDictionary), tạo RunLoop cho luồng nền hoặc tích hợp với thư viện C mong đợi pthread_t.
import Foundation
class ThreadManager {
// 1. Thread (cấp thấp)
func createThread() {
let thread = Thread {
// Mã được thực thi trên luồng mới
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// Hàng đợi song song
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD async task")
}
// Barrier để đồng bộ hóa ghi
queue.async(flags: .barrier) {
// Truy cập độc quyền trong khi ghi
print("Barrier write: exclusive access")
}
}
// 3. OperationQueue với phụ thuộc
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("Downloading...")
}
let process = BlockOperation {
print("Processing...")
}
let save = BlockOperation {
print("Saving...")
}
// Phụ thuộc: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// Bộ sưu tập thread-safe qua 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)
}
}
}
Lớp ThreadSafeArray minh họa mẫu Đọc đồng thời / Ghi độc quyền qua GCD barrier. Đọc qua queue.sync{} thực thi song song từ nhiều luồng. Ghi qua queue.async(flags: .barrier) chặn tất cả thao tác khác (cả đọc và ghi) cho đến khi hoàn thành ghi. Điều này hiệu quả hơn khối synchronized vì không chặn người đọc khi không có ghi.
Sử dụng trực tiếp Thread trong iOS hợp lý trong ba trường hợp: cho thread-local storage (Thread.current.threadDictionary) — lưu dữ liệu gắn với luồng; để tạo RunLoop đặc biệt trên luồng nền với performSelector:onThread:; để tích hợp với thư viện C/C++ mong đợi pthread_t. Trong tất cả các trường hợp khác, GCD qua DispatchQueue được ưu tiên hơn — nó tự động quản lý pool luồng và mức tiêu thụ năng lượng.
Race condition (điều kiện tranh đua) xảy ra khi hai hoặc nhiều luồng đồng thời truy cập dữ liệu chung và ít nhất một luồng thực hiện ghi. Kết quả phụ thuộc vào thứ tự thực thi (timing) và không thể dự đoán. Để ngăn chặn race condition, sử dụng các nguyên hàm đồng bộ hóa. Trong phát triển di động, có sẵn khóa (synchronized, NSLock), thao tác nguyên tử (AtomicInteger, thuộc tính atomic iOS) và hàng đợi (serial queue).
Chọn nguyên hàm phụ thuộc vào kịch bản. Cho bộ đếm đơn giản và cờ, thao tác nguyên tử là đủ (AtomicInteger, thuộc tính atomic). Cho các đoạn quan trọng với nhiều thao tác — khóa (synchronized, NSLock). Cho cấu trúc dữ liệu phức tạp — serial DispatchQueue hoặc GCD barrier. Khóa dễ hiểu hơn nhưng dễ bị deadlock và livelock. Hàng đợi phức tạp hơn nhưng an toàn hơn.
// Đồng bộ hóa trong Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — cho bộ đếm đơn giản
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — cho đoạn quan trọng
@Synchronized
fun synchronizedOperation() {
// Chỉ một luồng tại một thời điểm
doWork()
}
// 3. Mutex từ coroutine — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// protected code — thread-safe
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// Ví dụ Deadlock: A khóa B, B khóa 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 minh họa ba cách tiếp cận đồng bộ hóa. AtomicInteger.incrementAndGet() — thao tác nguyên tử không khóa (CAS). @Synchronized — monitor tích hợp Java, khóa toàn bộ đối tượng. Mutex.withLock — mutex coroutine, tạm dừng coroutine thay vì khóa luồng (hiệu quả hơn). DeadlockExample cho thấy deadlock cổ điển: hai luồng chiếm khóa theo thứ tự khác nhau.
Thread Pool (pool luồng) — tập hợp các luồng được tạo trước, được tái sử dụng để thực thi tác vụ. Thay vì tạo luồng mới cho mỗi tác vụ (tốn kém), pool lấy một luồng có sẵn từ pool. Nếu không có luồng trống, tác vụ được xếp vào hàng đợi. Pool tự động quản lý kích thước: luồng mới được tạo khi tải cao, luồng nhàn rỗi kết thúc. Điều này giảm chi phí tạo luồng hàng chục lần.
Trong Android, Executors.newFixedThreadPool(4) tạo pool 4 luồng. Nếu 10 tác vụ đến cùng lúc, 4 bắt đầu ngay, 6 chờ trong hàng đợi. Executors.newCachedThreadPool() tạo luồng khi cần (không giới hạn) và kết thúc luồng nhàn rỗi sau 60 giây. Đối với iOS, GCD tự động cung cấp pool hàng đợi toàn cục, kích thước tương ứng với số lõi CPU và tải hiện tại.
Trong Kotlin Coroutines, pool luồng được ẩn bên trong các dispatcher. Dispatchers.Default sử dụng pool có kích thước bằng số lõi CPU (tối thiểu 2). Dispatchers.IO — 64 luồng (đủ cho hàng trăm tác vụ IO-bound, vì hầu hết sẽ chờ I/O mà không chiếm CPU). Mỗi dispatcher tự động điều chỉnh kích thước pool theo tải, tiết kiệm pin khi nhàn rỗi.
Câu hỏi thường gặp
Thread — đơn vị thực thi mã cơ bản trong ứng dụng. Mỗi tiến trình có thể có nhiều luồng chia sẻ bộ nhớ nhưng có ngăn xếp riêng. Trong phát triển di động, các luồng được sử dụng để thực thi song song tác vụ mà không chặn UI. Android sử dụng Thread, Executors, HandlerThread và Coroutines. iOS sử dụng Thread, GCD (DispatchQueue) và OperationQueue.
Tạo Thread yêu cầu cấp phát ~1 MB ngăn xếp trong Android và ~512 KB trong iOS — đây là thao tác tốn kém. Với 1000 tác vụ, tạo trực tiếp 1000 luồng sẽ cần ~1 GB chỉ cho ngăn xếp cộng với chi phí context switch. Thay vì Thread, hãy sử dụng pool (Executors, GCD) hoặc coroutines — chúng tái sử dụng luồng, giảm chi phí hàng chục lần.
Race condition — hành vi không thể dự đoán khi nhiều luồng đồng thời truy cập dữ liệu chung có ghi. Có thể tránh bằng ba cách: sử dụng kiểu nguyên tử (AtomicInteger), khóa (synchronized, NSLock) hoặc tuần tự hóa truy cập qua hàng đợi (DispatchQueue serial, Actor trong Kotlin). Thực hành tốt nhất là giảm thiểu trạng thái mutable chung và sử dụng immutability.
Thread — đối tượng hệ thống gốc, chiếm ~1 MB ngăn xếp và gắn với lõi HĐH. Coroutine — đơn vị thực thi nhẹ của Kotlin, không gắn với luồng cụ thể và có thể tạm dừng (suspend) mà không chặn. Một luồng có thể thực thi hàng nghìn coroutine. Coroutine hiệu quả hơn về bộ nhớ và cho phép viết mã bất đồng bộ không callback.
Deadlock biểu hiện dưới dạng treo hoàn toàn ứng dụng mà không có ANR. Trong Android, sử dụng Thread.getAllStackTraces() để dump stack của tất cả luồng — hai luồng sẽ chờ khóa của nhau. Trong iOS — Thread.callStackSymbols. Công cụ: Android Studio Profiler (tab Threads), Instruments (iOS, Thread State View). Phòng ngừa: chiếm khóa theo thứ tự cố định, sử dụng tryLock với timeout.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm