Thread dalam pengembangan mobile — apa itu, jenis dan pengelolaan thread

Penulis: IT Sectr Diterbitkan: 2026-03-16 Waktu membaca: 11 mnt

Thread — satuan dasar waktu prosesor, yang memiliki stack sendiri dan berjalan secara independen dari thread lain. Dalam pengembangan mobile, thread digunakan untuk eksekusi paralel tugas agar antarmuka tetap responsif selama operasi yang berlangsung lama. Android mendukung java.lang.Thread, Executors dan Kotlin Coroutines, iOS — Thread (Objective-C), GCD dan OperationQueue. Menurut Android Thread Documentation, pembuatan thread native memerlukan alokasi ~1 MB untuk stack oleh sistem operasi.

Poin utama

  • Thread — unit penjadwalan CPU terkecil: setiap thread independen dan memiliki stack sendiri
  • Pembuatan thread memerlukan ~1 MB untuk stack di Android dan 512 KB di iOS, sehingga pool lebih efisien daripada pembuatan langsung
  • Android: Thread, Executors, HandlerThread, Coroutines — empat tingkat abstraksi thread
  • iOS: Thread (tingkat rendah), GCD (DispatchQueue), OperationQueue (tingkat tinggi)
  • Thread safety — akses bersama ke data mutable memerlukan sinkronisasi: locks, atomic, serial queues

Apa itu Thread

Thread (utas eksekusi) — rangkaian instruksi independen yang dapat dijadwalkan sistem operasi pada inti CPU. Setiap proses (aplikasi) berisi setidaknya satu thread — Main Thread. Thread tambahan dibuat untuk eksekusi paralel tugas. Setiap thread memiliki stack program sendiri (dengan variabel lokal), program counter (PC) dan register. Memori heap dibagi oleh semua thread dari proses tersebut.

Pada sistem operasi mobile, thread dijadwalkan dengan multitasking preemptif (preemptive multitasking): sistem operasi dapat menginterupsi eksekusi thread kapan saja dan menyerahkan kendali ke thread lain (context switch). Pengalihan konteks adalah operasi yang mahal (1–10 mikrodetik), karena memerlukan penyimpanan/pemulihan register CPU, pembaruan TLB dan pembersihan cache. Justru karena itulah jumlah thread yang berlebihan (ratusan dan ribuan) memperburuk kinerja — sistem operasi menghabiskan lebih banyak waktu untuk beralih daripada mengeksekusi.

Thread dan proses adalah konsep yang berbeda. Proses adalah instance aplikasi dengan memori virtual khusus. Thread di dalam proses berbagi memori ini dengan thread lain. Di Android, setiap komponen aplikasi (Activity, Service, BroadcastReceiver) bekerja dalam satu proses, tetapi dapat dieksekusi di thread yang berbeda. Aplikasi iOS juga merupakan satu proses dengan kemampuan membuat thread tambahan melalui GCD atau Thread.

Siklus hidup thread: status dan transisi

Setiap thread di Java/Kotlin (Android) dan NSThread (iOS) melewati lima status: New (dibuat), Runnable (siap dieksekusi), Running (sedang dieksekusi di CPU), Blocked/Waiting (menunggu sumber daya atau notifikasi), Terminated (selesai). Transisi antar status dikelola oleh penjadwal sistem operasi dan primitif sinkronisasi. Pengembang dapat memengaruhi prioritas thread (Thread.setPriority()) dan statusnya (sleep, join, interrupt).

Di Android, thread masuk ke status Blocked saat mencoba menguasai monitor yang sibuk (synchronized), memanggil Object.wait() atau Thread.sleep(). Di iOS — saat memanggil NSCondition.wait(), pthread_cond_wait() atau dispatch_semaphore_wait(). Dalam status Blocked, thread tidak mengonsumsi CPU, tetapi memakai memori (stack). Thread dapat diinterupsi (interrupted) dari thread lain, dengan menerima InterruptedException (Java) atau memeriksa isCancelled (Kotlin Coroutines).

Status Deskripsi Metode transisi
New Thread dibuat, tetapi belum dijalankan Konstruktor Thread()
Runnable Thread siap dieksekusi, menunggu CPU thread.start()
Running Thread sedang dieksekusi di inti CPU Penjadwal sistem operasi
Blocked/Waiting Thread menunggu sumber daya, monitor atau notifikasi synchronized, wait(), sleep()
Terminated Thread menyelesaikan run() atau diinterupsi run() selesai, interrupt()

Context Switch dan biayanya

Context switch (pengalihan konteks) — operasi di mana sistem operasi menyimpan status thread saat ini (register, PC, TLB) dan memuat status tersimpan dari thread lain. Pada sistem mobile (Linux + ART, XNU untuk iOS), context switch berlangsung 1–10 mikrodetik. Jika thread mengeksekusi tugas dalam 100 mikrodetik dan context switch memakan 5, maka 5% waktu terbuang. Untuk meminimalkan context switch, iOS menggunakan GCD dengan work stealing, dan Android menggunakan pool dengan fixedThreadCount.

Thread di Android: dari Thread hingga Coroutines

Android berevolusi dari java.lang.Thread tingkat rendah hingga coroutine modern. Setiap tingkat abstraksi memberikan lebih banyak kemampuan dengan overhead lebih kecil. Thread adalah kelas dasar, tetapi pembuatan langsung tidak disarankan: thread baru tidak dikelola oleh pool, sulit dipantau dan dibatalkan. AsyncTask (deprecated sejak API 30) adalah langkah maju, tetapi menderita kebocoran memori dan penanganan konfigurasi yang tidak nyaman.

HandlerThread — subkelas khusus Thread dengan Looper yang dapat memproses antrian pesan. Digunakan untuk eksekusi berurutan tugas di thread latar belakang, misalnya menulis data ke Room atau file. HandlerThread dibuat dengan panggilan start(), setelah itu melalui Handler(handlerThread.looper) dapat dikirim pesan dan Runnable. Panggilan handlerThread.quit() menghentikan Looper dan mengakhiri thread.

kotlin
// Android: Thread, HandlerThread dan Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors

class ThreadExample {

    // 1. Pembuatan langsung Thread (tidak disarankan)
    fun directThread() {
        val thread = Thread(Runnable {
            Thread.sleep(1000)
            print("Thread langsung dieksekusi")
        })
        thread.start()
    }

    // 2. HandlerThread untuk tugas latar belakang berurutan
    fun handlerThreadExample() {
        val handlerThread = HandlerThread("BackgroundQueue")
        handlerThread.start()

        val handler = Handler(handlerThread.looper)
        handler.post {
            // Eksekusi berurutan di thread latar belakang
            Thread.sleep(500)
            print("HandlerThread: tugas dieksekusi")
        }

        // Menghentikan thread (dieksekusi ketika tugas selesai)
        handlerThread.quitSafely()
    }

    // 3. Executors — pool 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 — standar modern
    suspend fun coroutineExample() = kotlinx.coroutines.withContext(
        kotlinx.coroutines.Dispatchers.Default
    ) {
        print("Coroutine on thread: ${Thread.currentThread().getName()}")
    }
}

Contoh ThreadExample menunjukkan keempat tingkat abstraksi thread di Android. Pembuatan langsung Thread adalah pendekatan paling rendah dan tidak efisien. HandlerThread berguna untuk tugas berurutan di latar belakang. Executors.newFixedThreadPool(4) membuat pool 4 thread untuk eksekusi paralel hingga 10 tugas. Kotlin Coroutines dengan Dispatchers.Default — cara yang modern, efisien dan aman.

HandlerThread: tugas latar belakang berurutan

HandlerThread — subkelas khusus Thread dengan Looper bawaan dan antrian pesan. Dibuat dengan panggilan start(), setelah itu melalui Handler(handlerThread.looper) dapat dikirim Runnable dan pesan. HandlerThread mengeksekusi tugas secara ketat berurutan — tugas berikutnya tidak dimulai sebelum tugas sebelumnya selesai. Ini nyaman untuk menulis data ke Room atau file, di mana urutan operasi sangat penting. Panggilan quitSafely() menghentikan Looper setelah tugas saat ini selesai.

Thread di iOS: Thread, GCD dan OperationQueue

iOS juga menyediakan tiga tingkat bekerja dengan thread. Thread (Thread di Swift, NSThread di Objective-C) — API tingkat rendah yang langsung membuat thread native. GCD (Grand Central Dispatch) melalui DispatchQueue — alat utama untuk pengembang iOS yang secara otomatis mengelola pool thread. OperationQueue — abstraksi tingkat tinggi di atas GCD dengan dukungan dependensi, prioritas dan pembatalan.

Penggunaan langsung Thread dalam pengembangan iOS modern sangat jarang — GCD menyediakan semua kemampuan yang diperlukan dengan pengelolaan memori dan thread secara otomatis. Thread hanya digunakan untuk kasus spesifik: menyiapkan thread-local storage (threadDictionary), membuat RunLoop untuk thread latar belakang, atau integrasi dengan pustaka C yang menunggu pthread_t.

swift
import Foundation

class ThreadManager {

    // 1. Thread (tingkat rendah)
    func createThread() {
        let thread = Thread {
            // Kode dieksekusi di thread baru
            print("Current thread: \(Thread.current)")
        }
        thread.name = "com.app.worker"
        thread.qualityOfService = .utility
        thread.start()
    }

    // 2. GCD — DispatchQueue
    func gcdExample() {
        // Antrian paralel
        let queue = DispatchQueue(label: "com.app.concurrent",
                                 qos: .utility,
                                 attributes: .concurrent)

        queue.async {
            print("Tugas async GCD")
        }

        // Barrier untuk sinkronisasi penulisan
        queue.async(flags: .barrier) {
            // Akses eksklusif selama penulisan
            print("Barrier write: akses eksklusif")
        }
    }

    // 3. OperationQueue dengan dependensi
    func operationQueueExample() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2
        queue.qualityOfService = .background

        let download = BlockOperation {
            print("Mengunduh...")
        }
        let process = BlockOperation {
            print("Memproses...")
        }
        let save = BlockOperation {
            print("Menyimpan...")
        }

        // Dependensi: download -> process -> save
        process.addDependency(download)
        save.addDependency(process)

        queue.addOperations([download, process, save], waitUntilFinished: false)
    }
}

// Koleksi thread-safe melalui 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)
        }
    }
}

Kelas ThreadSafeArray mendemonstrasikan pola Concurrent Read / Exclusive Write melalui GCD barrier. Pembacaan melalui queue.sync{} dieksekusi paralel dari beberapa thread. Penulisan melalui queue.async(flags: .barrier) memblokir semua operasi lain (baik baca maupun tulis) hingga penulisan selesai. Ini lebih efisien daripada blok synchronized, karena tidak memblokir pembaca selama tidak ada penulisan.

Thread iOS vs GCD: kapan menggunakan Thread secara langsung

Penggunaan langsung Thread di iOS dibenarkan dalam tiga kasus: untuk thread-local storage (Thread.current.threadDictionary) — menyimpan data yang terkait dengan thread; untuk membuat RunLoop khusus di thread latar belakang dengan performSelector:onThread:; untuk integrasi dengan pustaka C/C++ yang menunggu pthread_t. Dalam semua kasus lain, GCD melalui DispatchQueue lebih disukai — secara otomatis mengelola pool thread dan konsumsi daya.

Sinkronisasi thread: locks, atomic, serial queues

Race condition (kondisi balapan) terjadi ketika dua atau lebih thread secara bersamaan mengakses data bersama dan setidaknya salah satunya melakukan penulisan. Hasilnya bergantung pada urutan eksekusi (timing) dan tidak dapat diprediksi. Untuk mencegah race condition digunakan primitif sinkronisasi. Dalam pengembangan mobile tersedia penguncian (synchronized, NSLock), operasi atomik (AtomicInteger, properti atomic di iOS) dan antrian (serial queue).

Pemilihan primitif bergantung pada skenario. Untuk penghitung dan flag sederhana, operasi atomik (AtomicInteger, atomic property) sudah cukup. Untuk bagian kritis dengan beberapa operasi — penguncian (synchronized, NSLock). Untuk struktur data kompleks — serial DispatchQueue atau GCD barrier. Penguncian lebih mudah dipahami, tetapi rentan terhadap deadlock dan livelock. Antrian lebih rumit, tetapi lebih aman.

kotlin
// Sinkronisasi di Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class Counter {

    // 1. AtomicInteger — untuk penghitung sederhana
    private val atomicCount = AtomicInteger(0)
    fun incrementAtomic() = atomicCount.incrementAndGet()

    // 2. synchronized — untuk bagian kritis
    @Synchronized
    fun synchronizedOperation() {
        // Hanya satu thread pada satu waktu
        doWork()
    }

    // 3. Mutex dari coroutine — suspend-safe
    private val mutex = Mutex()
    suspend fun mutexOperation() {
        mutex.withLock {
            // protected code — thread-safe
            doWork()
        }
    }

    private fun doWork() { /* critical section */ }
}

// Contoh deadlock: A memblokir B, B memblokir 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 mendemonstrasikan tiga pendekatan sinkronisasi. AtomicInteger.incrementAndGet() — operasi atomik tanpa penguncian (CAS). @Synchronized — monitor bawaan Java, memblokir seluruh objek. Mutex.withLock — mutex coroutine, menjeda coroutine alih-alih memblokir thread (lebih efisien). DeadlockExample menunjukkan deadlock klasik: dua thread menguasai penguncian dalam urutan berbeda.

Thread Pools: mengapa Executors lebih baik dari Thread

Thread Pool (kumpulan thread) — sekumpulan thread yang dibuat sebelumnya dan digunakan kembali untuk mengeksekusi tugas. Alih-alih membuat thread baru untuk setiap tugas (mahal), pool mengambil thread bebas dari pool. Jika tidak ada thread bebas, tugas dimasukkan ke antrian. Pool secara otomatis mengelola ukurannya: thread baru dibuat saat beban puncak, thread yang tidak aktif diakhiri. Ini mengurangi overhead pembuatan thread hingga puluhan kali.

Di Android, Executors.newFixedThreadPool(4) membuat pool 4 thread. Jika 10 tugas masuk sekaligus, 4 akan segera mulai dan 6 akan menunggu dalam antrian. Executors.newCachedThreadPool() membuat thread sesuai kebutuhan (tanpa batas) dan mengakhiri thread yang tidak aktif setelah 60 detik. Untuk iOS, GCD secara otomatis menyediakan pool antrian global yang ukurannya sesuai dengan jumlah inti CPU dan beban saat ini.

Dalam Kotlin Coroutines, pool thread tersembunyi di dalam dispatcher. Dispatchers.Default menggunakan pool berukuran jumlah inti CPU (minimal 2). Dispatchers.IO — 64 thread (cukup untuk ratusan tugas IO-bound, karena sebagian besar akan menunggu input-output tanpa memakai CPU). Setiap dispatcher secara otomatis menskalakan pool sesuai beban, menghemat daya baterai saat tidak aktif.

Pertanyaan yang sering diajukan

Apa itu Thread dalam pengembangan mobile?

Thread — satuan dasar eksekusi kode dalam aplikasi. Setiap proses dapat memiliki banyak thread yang berbagi memori, tetapi dengan stack sendiri. Dalam pengembangan mobile, thread digunakan untuk eksekusi paralel tugas tanpa memblokir UI. Android menggunakan Thread, Executors, HandlerThread dan Coroutines. iOS menggunakan Thread, GCD (DispatchQueue) dan OperationQueue.

Mengapa tidak disarankan membuat Thread secara langsung?

Pembuatan Thread memerlukan alokasi ~1 MB untuk stack di Android dan ~512 KB di iOS — ini operasi yang mahal. Untuk 1000 tugas, pembuatan langsung 1000 thread akan memerlukan ~1 GB hanya untuk stack ditambah overhead context switch. Alih-alih Thread, gunakan pool (Executors, GCD) atau coroutine — mereka menggunakan kembali thread, mengurangi overhead hingga puluhan kali.

Apa itu race condition dan bagaimana menghindarinya?

Race condition — perilaku tidak dapat diprediksi saat beberapa thread mengakses data bersama dengan penulisan secara bersamaan. Dapat dihindari dengan tiga cara: menggunakan tipe atomik (AtomicInteger), penguncian (synchronized, NSLock) atau menyerialkan akses melalui antrian (DispatchQueue serial, Actor di Kotlin). Praktik terbaik — meminimalkan status mutable bersama dan menggunakan immutability.

Apa perbedaan Thread dengan coroutine?

Thread — objek sistem native yang memakan ~1 MB stack dan terkait dengan kernel sistem operasi. Coroutine — unit eksekusi ringan di Kotlin yang tidak terkait dengan thread tertentu dan dapat dijeda (suspend) tanpa memblokir. Satu thread dapat mengeksekusi ribuan coroutine. Coroutine lebih efisien dalam memori dan memungkinkan menulis kode asinkron tanpa callbacks.

Bagaimana menangkap deadlock dalam aplikasi mobile?

Deadlock bermanifestasi sebagai aplikasi yang macet total tanpa ANR. Di Android gunakan Thread.getAllStackTraces() untuk dump stack semua thread — dua thread akan saling menunggu penguncian. Di iOS — Thread.callStackSymbols. Alat: Android Studio Profiler (tab Threads), Instruments (iOS, Thread State View). Pencegahan: kuasai penguncian dalam urutan tetap, gunakan tryLock dengan timeout.

Kesimpulan

  • Thread — unit terkecil CPU: eksekusi independen dengan stack sendiri, memori heap bersama
  • Lima status thread: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android berevolusi dari Thread → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS menyediakan Thread, GCD (DispatchQueue) dan OperationQueue — dari rendah ke tinggi
  • Race condition diselesaikan dengan penguncian (synchronized, NSLock), tipe atomik dan antrian serial
  • Deadlock terjadi saat penguncian saling silang — dicegah dengan urutan tetap
  • Thread Pool lebih efisien daripada membuat Thread baru: menggunakan kembali thread, mengurangi overhead context switch

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga