Thread — ang pangunahing yunit ng oras ng processor, na may sariling stack at gumagana nang hiwalay sa iba pang mga thread. Sa mobile development, ginagamit ang mga thread para sa parallel na pagpapatupad ng mga gawain upang manatiling responsive ang interface sa panahon ng mahahabang operasyon. Sinusuportahan ng Android ang java.lang.Thread, Executors at Kotlin Coroutines, iOS — Thread (Objective-C), GCD at OperationQueue. Ayon sa Android Thread Documentation, ang paggawa ng native na thread ay nangangailangan ng paglalaan ng ~1 MB para sa stack ng operating system.
Mga pangunahing punto
Thread (thread ng pagpapatupad) — isang independiyenteng pagkakasunod-sunod ng mga instruksiyon na maaaring i-schedule ng operating system sa isang CPU core. Ang bawat proseso (application) ay naglalaman ng hindi bababa sa isang thread — Main Thread. Ang mga karagdagang thread ay ginagawa para sa parallel na pagpapatupad ng mga gawain. Bawat thread ay may sariling program stack (na may mga lokal na variable), program counter (PC) at mga register. Ang heap memory ay ibinabahagi ng lahat ng thread ng proseso.
Sa mga mobile operating system, ang mga thread ay naka-schedule sa pamamagitan ng preemptive multitasking: maaaring i-interrupt ng operating system ang pagpapatupad ng isang thread anumang oras at ibigay ang kontrol sa iba (context switch). Ang paglipat ng konteksto ay isang mahal na operasyon (1–10 microsecond), dahil nangangailangan ito ng pag-save/pag-restore ng mga CPU register, pag-update ng TLB at paglilinis ng mga cache. Kaya naman ang labis na bilang ng mga thread (daan-daan at libo-libo) ay nagpapabagal ng performance — mas maraming oras ang ginugugol ng operating system sa paglipat kaysa sa pagpapatupad.
Thread at proseso ay magkaibang konsepto. Ang proseso ay isang instance ng application na may nakalaang virtual memory. Ang thread sa loob ng proseso ay nagbabahagi ng memorya na ito sa iba pang mga thread. Sa Android, bawat component ng application (Activity, Service, BroadcastReceiver) ay gumagana sa isang proseso, ngunit maaaring tumakbo sa iba't ibang thread. Ang iOS application ay isa ring proseso na may kakayahang gumawa ng mga karagdagang thread sa pamamagitan ng GCD o Thread.
Bawat thread sa Java/Kotlin (Android) at NSThread (iOS) ay dumadaan sa limang estado: New (gawa), Runnable (handa nang tumakbo), Running (tumatakbo sa CPU), Blocked/Waiting (naghihintay ng resource o notifikasyon), Terminated (tapos). Ang mga paglipat sa pagitan ng mga estado ay pinamamahalaan ng scheduler ng operating system at mga synchronization primitive. Maaaring maimpluwensyahan ng developer ang priority ng thread (Thread.setPriority()) at ang estado nito (sleep, join, interrupt).
Sa Android, ang thread ay pumapasok sa estado na Blocked kapag sinusubukang makuha ang isang abalang monitor (synchronized), pagtawag sa Object.wait() o Thread.sleep(). Sa iOS — kapag tumatawag ng NSCondition.wait(), pthread_cond_wait() o dispatch_semaphore_wait(). Sa estado na Blocked, ang thread ay hindi gumagamit ng CPU, ngunit sumasakop ng memorya (stack). Maaaring ma-interrupt (interrupted) ang thread mula sa ibang thread, sa pamamagitan ng pagtanggap ng InterruptedException (Java) o pag-check ng isCancelled (Kotlin Coroutines).
| Estado | Paglalarawan | Paraan ng paglipat |
|---|---|---|
| New | Ang thread ay gawa, ngunit hindi pa pinapatakbo | Constructor ng Thread() |
| Runnable | Ang thread ay handa nang tumakbo, naghihintay ng CPU | thread.start() |
| Running | Ang thread ay tumatakbo sa CPU core | Scheduler ng operating system |
| Blocked/Waiting | Ang thread ay naghihintay ng resource, monitor o notifikasyon | synchronized, wait(), sleep() |
| Terminated | Ang thread ay natapos ang run() o na-interrupt | run() tapos, interrupt() |
Context switch (paglipat ng konteksto) — isang operasyon kung saan ini-save ng operating system ang estado ng kasalukuyang thread (mga register, PC, TLB) at nilo-load ang na-save na estado ng isa pa. Sa mga mobile system (Linux + ART, XNU para sa iOS), ang context switch ay tumatagal ng 1–10 microsecond. Kung ang thread ay nagpapatupad ng gawain sa loob ng 100 microsecond at ang context switch ay tumatagal ng 5, 5% ng oras ang nasasayang. Upang mabawasan ang context switch, gumagamit ang iOS ng GCD na may work stealing, at Android — mga pool na may fixedThreadCount.
Android ay umunlad mula sa mababang antas na java.lang.Thread hanggang sa modernong coroutine. Bawat antas ng abstraction ay nagbibigay ng mas maraming kakayahan na may mas kaunting overhead. Ang Thread ay ang base class, ngunit ang direktang paggawa nito ay hindi inirerekomenda: ang bagong thread ay hindi pinamamahalaan ng pool, mahirap itong i-monitor at i-cancel. Ang AsyncTask (deprecated mula API 30) ay isang hakbang pasulong, ngunit nagdusa sa memory leaks at hindi maginhawang pagproseso ng mga configuration.
Ang HandlerThread — espesyal na subclass ng Thread na may Looper, na maaaring magproseso ng queue ng mga mensahe. Ginagamit para sa sunod-sunod na pagpapatupad ng mga gawain sa background thread, halimbawa, pagsusulat ng data sa Room o mga file. Ang HandlerThread ay ginagawa sa pamamagitan ng tawag na start(), pagkatapos nito sa pamamagitan ng Handler(handlerThread.looper) maaaring magpadala ng mga mensahe at Runnable. Ang tawag na handlerThread.quit() ay humihinto sa Looper at tinatapos ang thread.
// Android: Thread, HandlerThread at Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. Direktang paggawa ng Thread (hindi inirerekomenda)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("Direktang thread na naisagawa")
})
thread.start()
}
// 2. HandlerThread para sa sunod-sunod na mga background task
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Sunod-sunod na pagpapatupad sa background thread
Thread.sleep(500)
print("HandlerThread: tapos ang gawain")
}
// Paghinto ng thread (isinasagawa kapag tapos na ang mga gawain)
handlerThread.quitSafely()
}
// 3. Executors — pool ng thread
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 — modernong standard
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
Ang halimbawang ThreadExample ay nagpapakita ng lahat ng apat na antas ng abstraction ng thread sa Android. Ang direktang paggawa ng Thread ay ang pinakamababang antas at hindi epektibong pamamaraan. Ang HandlerThread ay kapaki-pakinabang para sa sunod-sunod na mga gawain sa background. Ang Executors.newFixedThreadPool(4) ay gumagawa ng pool ng 4 na thread para sa parallel na pagpapatupad ng hanggang 10 gawain. Ang Kotlin Coroutines na may Dispatchers.Default — moderno, epektibo at ligtas na paraan.
HandlerThread — isang espesyalisadong subclass ng Thread na may built-in na Looper at queue ng mga mensahe. Ginagawa sa pamamagitan ng tawag na start(), pagkatapos nito sa pamamagitan ng Handler(handlerThread.looper) maaaring magpadala ng Runnable at mga mensahe. Ang HandlerThread ay nagpapatupad ng mga gawain nang mahigpit na sunod-sunod — ang susunod na gawain ay hindi magsisimula hangga't hindi natatapos ang nauna. Ito ay maginhawa para sa pagsusulat ng data sa Room o mga file, kung saan mahalaga ang pagkakasunod-sunod ng mga operasyon. Ang tawag na quitSafely() ay humihinto sa Looper pagkatapos matapos ang kasalukuyang gawain.
iOS ay nagbibigay din ng tatlong antas ng pagtatrabaho sa mga thread. Thread (Thread sa Swift, NSThread sa Objective-C) — mababang antas na API na direktang gumagawa ng native na thread. GCD (Grand Central Dispatch) sa pamamagitan ng DispatchQueue — pangunahing kasangkapan para sa mga iOS developer, na awtomatikong namamahala ng pool ng mga thread. OperationQueue — mataas na antas na abstraction sa itaas ng GCD na may suporta sa mga dependency, priority at pag-cancel.
Ang direktang paggamit ng Thread sa modernong iOS development ay napakabihirang — nagbibigay ang GCD ng lahat ng kinakailangang kakayahan na may awtomatikong pamamahala ng memorya at mga thread. Ang Thread ay ginagamit lamang para sa mga partikular na kaso: pag-set up ng thread-local storage (threadDictionary), paggawa ng RunLoop para sa background thread o integrasyon sa mga C library na naghihintay ng pthread_t.
import Foundation
class ThreadManager {
// 1. Thread (mababang antas)
func createThread() {
let thread = Thread {
// Ang code ay isinasagawa sa bagong thread
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// Parallel na queue
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD async task")
}
// Barrier para sa synchronization ng pagsulat
queue.async(flags: .barrier) {
// Eksklusibong access habang nagsusulat
print("Barrier write: eksklusibong access")
}
}
// 3. OperationQueue na may mga dependency
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("Nagda-download...")
}
let process = BlockOperation {
print("Nagpoproseso...")
}
let save = BlockOperation {
print("Nagse-save...")
}
// Mga dependency: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// Thread-safe collection sa pamamagitan ng 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)
}
}
}
Ang klase na ThreadSafeArray ay nagpapakita ng pattern na Concurrent Read / Exclusive Write sa pamamagitan ng GCD barrier. Ang pagbasa sa pamamagitan ng queue.sync{} ay isinasagawa nang parallel mula sa maraming thread. Ang pagsulat sa pamamagitan ng queue.async(flags: .barrier) ay humaharang sa lahat ng iba pang operasyon (kapwa pagbasa at pagsulat) hanggang matapos ang pagsulat. Ito ay mas epektibo kaysa sa mga block na synchronized, dahil hindi nito hinaharangan ang mga mambabasa habang walang pagsusulat.
Ang direktang paggamit ng Thread sa iOS ay makatwiran sa tatlong kaso: para sa thread-local storage (Thread.current.threadDictionary) — pag-iimbak ng data na nakatali sa thread; para sa paggawa ng espesyal na RunLoop sa background thread na may performSelector:onThread:; para sa integrasyon sa mga C/C++ library na naghihintay ng pthread_t. Sa lahat ng iba pang kaso, mas mainam ang GCD sa pamamagitan ng DispatchQueue — awtomatikong pinamamahalaan nito ang pool ng mga thread at ang paggamit ng enerhiya.
Race condition (kondisyon ng karera) ay nangyayari kapag dalawa o higit pang mga thread ang sabay-sabay na uma-access sa shared data at hindi bababa sa isa sa kanila ang nagsusulat. Ang resulta ay nakadepende sa pagkakasunod-sunod ng pagpapatupad (timing) at hindi mahulaan. Para maiwasan ang race condition, ginagamit ang mga synchronization primitive. Sa mobile development, available ang mga lock (synchronized, NSLock), mga atomic na operasyon (AtomicInteger, mga atomic property sa iOS) at mga queue (serial queue).
Pagpili ng primitive ay nakadepende sa sitwasyon. Para sa mga simpleng counter at flag, sapat na ang mga atomic na operasyon (AtomicInteger, atomic property). Para sa mga kritikal na seksyon na may maraming operasyon — mga lock (synchronized, NSLock). Para sa mga kumplikadong istruktura ng data — serial DispatchQueue o GCD barrier. Mas madaling maunawaan ang mga lock, ngunit madaling kapitan ng deadlock at livelock. Mas kumplikado ang mga queue, ngunit mas ligtas.
// Synchronization sa Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — para sa mga simpleng counter
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — para sa mga kritikal na seksyon
@Synchronized
fun synchronizedOperation() {
// Isang thread lamang sa bawat pagkakataon
doWork()
}
// 3. Mutex mula sa coroutine — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// protected code — thread-safe
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// Halimbawa ng deadlock: A humaharang sa B, B humaharang sa 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") }
}
}
Ang Counter ay nagpapakita ng tatlong pamamaraan ng synchronization. AtomicInteger.incrementAndGet() — isang atomic na operasyon nang walang mga lock (CAS). @Synchronized — ang built-in na monitor ng Java, hinaharangan ang buong object. Mutex.withLock — coroutine mutex, pinapa-pause ang coroutine sa halip na harangan ang thread (mas epektibo). Ipinapakita ng DeadlockExample ang klasikong deadlock: dalawang thread ang kumukuha ng mga lock sa magkaibang pagkakasunod-sunod.
Thread Pool (pool ng mga thread) — isang hanay ng mga naunang ginawang thread na muling ginagamit para sa pagpapatupad ng mga gawain. Sa halip na gumawa ng bagong thread para sa bawat gawain (mahal), kumukuha ang pool ng libreng thread mula sa pool. Kung walang libreng thread, inilalagay ang gawain sa queue. Awtomatikong pinamamahalaan ng pool ang laki nito: gumagawa ng mga bagong thread sa peak load, tinatapos ang mga idle thread. Binabawasan nito ang overhead ng paggawa ng thread ng sampung beses.
Sa Android, ang Executors.newFixedThreadPool(4) ay gumagawa ng pool ng 4 na thread. Kung sabay-sabay na dumarating ang 10 gawain, 4 ang agad na magsisimula, at 6 ang maghihintay sa queue. Ang Executors.newCachedThreadPool() ay gumagawa ng mga thread ayon sa pangangailangan (walang limitasyon) at tinatapos ang mga idle thread pagkatapos ng 60 segundo. Para sa iOS, awtomatikong nagbibigay ang GCD ng mga pool ng global queue, na ang laki ay tumutugma sa bilang ng mga CPU core at sa kasalukuyang load.
Sa Kotlin Coroutines, nakatago ang mga pool ng thread sa loob ng mga dispatcher. Ang Dispatchers.Default ay gumagamit ng pool na may sukat na katumbas ng bilang ng mga CPU core (minimum 2). Ang Dispatchers.IO — 64 na thread (sapat para sa daan-daang IO-bound na gawain, dahil karamihan ay maghihintay ng input-output nang hindi sumasakop ng CPU). Awtomatikong ini-scale ng bawat dispatcher ang pool ayon sa load, nakakatipid ng baterya sa panahon ng idle.
Mga madalas itanong
Thread — ang pangunahing yunit ng pagpapatupad ng code sa isang application. Bawat proseso ay maaaring magkaroon ng maraming thread na nagbabahagi ng memorya, ngunit may sariling stack. Sa mobile development, ginagamit ang mga thread para sa parallel na pagpapatupad ng mga gawain nang hindi hinaharangan ang UI. Gumagamit ang Android ng Thread, Executors, HandlerThread at Coroutines. Gumagamit ang iOS ng Thread, GCD (DispatchQueue) at OperationQueue.
Paggawa ng Thread ay nangangailangan ng paglalaan ng ~1 MB para sa stack sa Android at ~512 KB sa iOS — ito ay mahal na operasyon. Para sa 1000 gawain, ang direktang paggawa ng 1000 thread ay mangangailangan ng ~1 GB para lamang sa mga stack, dagdag pa ang overhead ng context switch. Sa halip na Thread, gumamit ng mga pool (Executors, GCD) o coroutine — muli nilang ginagamit ang mga thread, binabawasan ang overhead ng sampung beses.
Race condition — hindi mahuhulaan na pag-uugali kapag sabay-sabay na ina-access ng maraming thread ang shared data na may pagsusulat. Maiiwasan ito sa tatlong paraan: paggamit ng mga atomic type (AtomicInteger), mga lock (synchronized, NSLock) o pagseserialise ng access sa pamamagitan ng queue (serial DispatchQueue, Actor sa Kotlin). Pinakamahusay na kasanayan — i-minimize ang shared mutable state at gumamit ng immutability.
Thread — isang native na system object na sumasakop ng ~1 MB ng stack at nakatali sa kernel ng operating system. Coroutine — isang magaan na yunit ng pagpapatupad sa Kotlin na hindi nakatali sa isang partikular na thread at maaaring i-pause (suspend) nang hindi humaharang. Ang isang thread ay maaaring magpatupad ng libu-libong coroutine. Mas epektibo ang mga coroutine sa memorya at nagbibigay-daan sa pagsusulat ng asynchronous code nang walang callbacks.
Deadlock ay nagpapakita bilang kumpletong pag-freeze ng application nang walang ANR. Sa Android, gamitin ang Thread.getAllStackTraces() para i-dump ang mga stack ng lahat ng thread — dalawang thread ang maghihintay sa mga lock ng isa't isa. Sa iOS — Thread.callStackSymbols. Mga kasangkapan: Android Studio Profiler (tab na Threads), Instruments (iOS, Thread State View). Pag-iwas: kumuha ng mga lock sa nakapirming pagkakasunod-sunod, gumamit ng tryLock na may timeout.
Konklusyon
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din