Thread sa mobile development — ano ito, mga uri at pamamahala ng mga thread

May-akda: IT Sectr Nai-publish: 2026-03-16 Oras ng pagbabasa: 11 min

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 — ang pinakamaliit na yunit ng CPU scheduling: bawat thread ay independiyente at may sariling stack
  • Paggawa ng thread ay nangangailangan ng ~1 MB para sa stack sa Android at 512 KB sa iOS, kaya mas epektibo ang mga pool kaysa sa direktang paggawa
  • Android: Thread, Executors, HandlerThread, Coroutines — apat na antas ng abstraction ng thread
  • iOS: Thread (mababang antas), GCD (DispatchQueue), OperationQueue (mataas na antas)
  • Thread safety — ang pinagsasaluhang pag-access sa mutable data ay nangangailangan ng synchronization: locks, atomic, serial queues

Ano ang Thread

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.

Siklo ng buhay ng thread: mga estado at paglipat

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 at ang gastos nito

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.

Thread sa Android: mula Thread hanggang Coroutines

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.

kotlin
// 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: sunod-sunod na mga background task

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.

Thread sa iOS: Thread, GCD at OperationQueue

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.

swift
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.

Thread sa iOS vs GCD: kailan direktang gagamitin ang Thread

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.

Synchronization ng mga thread: locks, atomic, serial queues

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.

kotlin
// 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 Pools: bakit mas mahusay ang Executors kaysa Thread

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

Ano ang Thread sa mobile development?

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.

Bakit hindi inirerekomenda ang direktang paggawa ng Thread?

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.

Ano ang race condition at paano ito maiiwasan?

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.

Ano ang pagkakaiba ng Thread sa coroutine?

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.

Paano mahuli ang deadlock sa isang mobile application?

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

  • Thread — pinakamaliit na yunit ng CPU: independiyenteng pagpapatupad na may sariling stack, shared ang heap memory
  • Limang estado ng thread: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android ay umunlad mula Thread → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS ay nagbibigay ng Thread, GCD (DispatchQueue) at OperationQueue — mula mababa hanggang mataas
  • Race condition ay nalulutas sa mga lock (synchronized, NSLock), atomic type at serial queue
  • Deadlock ay nangyayari sa cross-capture ng mga lock — maiiwasan sa nakapirming pagkakasunod-sunod
  • Thread Pool ay mas epektibo kaysa paggawa ng mga bagong Thread: muling ginagamit ang mga thread, binabawasan ang overhead ng context switch

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.

Pag-usapan ang proyekto

Basahin din