Thread Pool dalam pengembangan mobile — dasar, kumpulan utas dan prinsip kerja

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

Thread Pool — adalah mekanisme manajemen utas di mana kumpulan utas yang dibuat sebelumnya digunakan kembali untuk menjalankan tugas, menghindari biaya overhead pembuatan dan penghancuran utas. Dalam pengembangan mobile, kumpulan utas digunakan untuk operasi latar belakang: permintaan jaringan, pemrosesan gambar, bekerja dengan basis data. Menurut Google Android Documentation (2025), ExecutorService adalah cara yang direkomendasikan untuk mengelola utas latar belakang di Android. Di iOS, peran serupa dilakukan oleh OperationQueue dan GCD DispatchQueue dengan antrean konkuren global.

Poin utama

  • Thread Pool — kumpulan utas yang dapat digunakan kembali untuk menjalankan tugas latar belakang tanpa biaya overhead pembuatan utas.
  • ExecutorService di Android mengelola pool melalui ThreadPoolExecutor dengan parameter yang dapat dikonfigurasi.
  • OperationQueue di iOS merangkum kumpulan utas melalui maxConcurrentOperationCount.
  • Core pool size — jumlah minimum utas yang selalu siap menjalankan tugas.
  • Work queue menyimpan tugas yang menunggu utas kosong di pool.

Apa itu Thread Pool?

Thread Pool (kumpulan utas) — adalah pola arsitektur di mana sejumlah tetap utas dibuat sebelumnya dan digunakan kembali untuk menjalankan beberapa tugas. Alih-alih membuat utas baru untuk setiap operasi (yang mahal: sekitar 1 MB memori stack per utas di JVM), tugas ditempatkan dalam antrean dan dijalankan oleh utas kosong dari pool. Dalam pengembangan mobile, kumpulan utas sangat penting untuk kinerja — Android dan iOS membatasi jumlah utas per aplikasi.

Mengapa Thread Pool penting dalam pengembangan mobile

Membuat utas adalah operasi yang mahal: alokasi stack, pendaftaran di sistem, peralihan konteks. Pada perangkat mobile dengan sumber daya terbatas, pembuatan utas yang tidak terkendali menyebabkan OOM (OutOfMemoryError) di Android dan throttling di iOS. Thread Pool memecahkan kedua masalah: membatasi jumlah maksimum utas yang berjalan bersamaan dan menggunakan kembali utas yang sudah dibuat. Google merekomendasikan ExecutorService daripada raw Thread(), Apple merekomendasikan OperationQueue daripada Thread.

ParameterTanpa pool (raw Thread)Dengan Thread Pool
Pembuatan utasUntuk setiap tugasSatu kali saat pembuatan pool
Maksimum utasTidak terbatas (risiko OOM)Dibatasi oleh core/max pool size
PemanfaatanRendah (utas mati setelah tugas)Tinggi (utas digunakan kembali)
ManajemenManual (join, interrupt)Otomatis (ExecutorService)
Konsumsi memoriMeningkat dengan setiap tugasTetap

Bagaimana cara kerja Thread Pool dalam pengembangan mobile?

Kumpulan utas bekerja berdasarkan prinsip Producer-Consumer: tugas (Runnable/Callable) ditempatkan dalam antrean pemblokiran (BlockingQueue). Utas dari pool menunggu tugas dalam antrean dan mengambilnya untuk dieksekusi. Algoritma: jika utas kosong lebih sedikit dari corePoolSize, utas baru dibuat. Jika corePoolSize tercapai, tugas ditempatkan dalam antrean. Jika antrean penuh dan utas lebih sedikit dari maximumPoolSize, utas tambahan dibuat. Saat maximumPoolSize terlampaui, tugas ditolak melalui RejectedExecutionHandler.

Core Pool Size vs Maximum Pool Size

Core pool size — jumlah utas yang dipertahankan dalam pool bahkan dalam keadaan diam. Maximum pool size — jumlah maksimum utas yang dapat dibuat saat antrean meluap. Perbedaan di antara keduanya adalah utas tambahan (overflow) yang dibuat sementara dan diakhiri setelah batas waktu idle. Pada perangkat mobile, disarankan untuk menetapkan corePoolSize sama dengan maximumPoolSize untuk menghindari beban puncak pembuatan utas.

Work Queue dan RejectedExecutionHandler

BlockingQueue menyimpan tugas yang menunggu eksekusi. Implementasi paling populer: LinkedBlockingQueue (tak terbatas), ArrayBlockingQueue (terbatas) dan SynchronousQueue (tanpa penyimpanan — tugas langsung diteruskan ke utas). Saat antrean dan pool penuh, RejectedExecutionHandler diaktifkan. Kebijakan standar: AbortPolicy (melempar RejectedExecutionException), CallerRunsPolicy (mengeksekusi di utas pengirim), DiscardPolicy dan DiscardOldestPolicy.

kotlin
// Membuat Thread Pool di Android
val threadPool = ThreadPoolExecutor(
    corePoolSize = 2,        // Minimal 2 utas
    maximumPoolSize = 4,     // Maksimal 4 utas
    keepAliveTime = 30L,     // Masa pakai utas overflow
    unit = TimeUnit.SECONDS,
    workQueue = LinkedBlockingQueue<Runnable>(16),
    threadFactory = Executors.defaultThreadFactory(),
    handler = ThreadPoolExecutor.CallerRunsPolicy()
)

// Mengirim tugas
threadPool.execute {
    val result = api.fetchData()
    runOnUiThread { showData(result) }
}

// Mengakhiri pool
threadPool.shutdown()
// Menunggu penyelesaian semua tugas
threadPool.awaitTermination(10, TimeUnit.SECONDS)

Thread Pool di Android: ExecutorService

Android menyediakan beberapa implementasi kumpulan utas melalui java.util.concurrent. Executors — pabrik dengan konfigurasi siap pakai: newFixedThreadPool(n) (pool tetap), newCachedThreadPool() (tak terbatas, utas dibuat sesuai kebutuhan), newSingleThreadExecutor() (satu utas — eksekusi berurutan). Untuk proyek mobile, newFixedThreadPool dengan batas wajar (2-4 utas) direkomendasikan, karena cached pool dapat membuat terlalu banyak utas.

ThreadPoolExecutor di Android

ThreadPoolExecutor (TPE) — implementasi lengkap ExecutorService dengan parameter yang dapat dikonfigurasi. Di Android, TPE digunakan di dalam AsyncTask, IntentService dan JobIntentService. Parameter corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue dan RejectedExecutionHandler memungkinkan penyesuaian perilaku pool secara detail. Rekomendasi untuk Android: corePoolSize = jumlah inti CPU - 1 (untuk tugas IO-bound) atau jumlah inti (untuk tugas CPU-bound). Untuk aplikasi tipikal — 2-4 utas.

kotlin
// Konfigurasi Executors siap pakai
// 1. Pool tetap untuk 3 utas
val fixedPool = Executors.newFixedThreadPool(3)

// 2. Pool cache (tidak disarankan untuk mobile)
val cachedPool = Executors.newCachedThreadPool()

// 3. Utas tunggal (serialisasi)
val singlePool = Executors.newSingleThreadExecutor()

// 4. Penjadwal (tugas periodik)
val scheduler = Executors.newScheduledThreadPool(2)

// Penggunaan dengan Callable dan Future
val future: Future<String> = fixedPool.submit(Callable {
    "Result: ${api.call()}"
})
// Mendapatkan hasil (memblokir utas)
val result = future.get(5, TimeUnit.SECONDS)

// Mengakhiri pool
fixedPool.shutdownNow()

CoroutineDispatcher sebagai kumpulan utas

Kotlin coroutine menyediakan CoroutineDispatcher — abstraksi yang mirip dengan kumpulan utas. Dispatchers.IO menggunakan pool 64 utas (terbatas). Dispatchers.Default — pool sama dengan jumlah inti CPU. CoroutineDispatcher tidak memerlukan shutdown manual dan dikelola secara otomatis. Untuk penyesuaian yang detail, buat ExecutorCoroutineDispatcher Anda sendiri melalui Executors.newFixedThreadPool(2).asCoroutineDispatcher(). Coroutine tidak menggantikan kumpulan utas, tetapi membungkusnya.

Thread Pool di iOS: OperationQueue dan GCD

iOS menyediakan dua mekanisme utama untuk mengelola kumpulan utas: OperationQueue (API tingkat tinggi berbasis GCD) dan GCD DispatchQueue (API tingkat rendah C). OperationQueue merangkum kumpulan utas melalui properti maxConcurrentOperationCount. Secara default, OperationQueue menggunakan system-defined maximum (tergantung pada beban sistem). DispatchQueue.global() menyediakan antrean konkuren dengan kumpulan utas sistem.

OperationQueue dan maxConcurrentOperationCount

OperationQueue mengelola kumpulan utas melalui maxConcurrentOperationCount. Nilai 1 membuat antrean serial (mirip dengan single thread pool). Nilai lebih dari 1 — pool konkuren dengan batas yang ditentukan. Secara default, maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (optimum sistem, biasanya 4-8 utas). Operation mendukung dependensi, prioritas dan pembatalan. Setiap operasi dijalankan pada utas kosong mana pun dari pool sistem.

swift
// OperationQueue dengan pool 3 utas
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility

// Membuat operasi
let operation1 = BlockOperation {
    let data = fetchData(from: url1)
    DispatchQueue.main.async { updateUI(data) }
}

let operation2 = BlockOperation {
    let data = fetchData(from: url2)
    DispatchQueue.main.async { updateUI(data) }
}

// Ketergantungan: operation2 menunggu operation1
operation2.addDependency(operation1)

// Menambahkan ke antrian
queue.addOperations([operation1, operation2], waitUntilFinished: false)

// Membatalkan semua operasi
queue.cancelAllOperations()

GCD DispatchQueue sebagai kumpulan utas

DispatchQueue — adalah kumpulan utas dari Apple. Antrean konkuren (qos: .utility) menggunakan kumpulan utas sistem, yang dioptimalkan untuk beban perangkat saat ini. QoS yang berbeda (userInteractive, userInitiated, utility, background) dipetakan ke pool yang berbeda dengan prioritas yang berbeda. DispatchGroup memungkinkan sinkronisasi beberapa tugas. DispatchWorkItem mendukung pembatalan dan qualityOfService. Untuk kontrol yang detail, buat antrean konkuren Anda sendiri melalui DispatchQueue(label: qos: attributes: .concurrent).

swift
// GCD DispatchQueue sebagai kumpulan utas
let customQueue = DispatchQueue(
    label: "com.app.background",
    qos: .utility,
    attributes: .concurrent,
    autoreleaseFrequency: .workItem
)

// Mengirim tugas ke pool
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }

// DispatchGroup untuk sinkronisasi
let group = DispatchGroup()
let pool = DispatchQueue.global(qos: .utility)

pool.async(group: group) { fetchData() }
pool.async(group: group) { processImage() }

group.notify(queue: .main) {
    self.showResult() // Kedua tugas selesai
}

// Membatasi konkurensi melalui semaphore
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    pool.async {
        semaphore.wait()
        download(url)
        semaphore.signal()
    }
}

Parameter konfigurasi Thread Pool

Konfigurasi kumpulan utas secara langsung memengaruhi kinerja aplikasi. Parameter yang salah menyebabkan kurangnya pemanfaatan CPU (terlalu sedikit utas) atau kelebihan beban sistem (terlalu banyak). Dalam aplikasi mobile, nilai optimal berbeda dari nilai server karena sumber daya yang terbatas dan konsumsi daya. Parameter utama: corePoolSize, maxPoolSize, queue capacity dan keepAliveTime.

Perhitungan ukuran pool optimal

Rumus untuk tugas IO-bound: corePoolSize = jumlah inti CPU × 2 (utas menunggu input-output). Untuk tugas CPU-bound: corePoolSize = jumlah inti CPU (utas terus-menerus sibuk dengan perhitungan). Pada perangkat mobile modern (6-8 inti), ini menghasilkan 6-8 utas untuk CPU-bound dan 12-16 untuk IO-bound. Tes praktis menunjukkan bahwa untuk aplikasi mobile tipikal, 3-4 utas adalah optimal — lebih banyak utas meningkatkan konsumsi daya tanpa peningkatan kinerja.

Queue Capacity dan perilaku saat meluap

Ukuran antrean tugas (work queue) menentukan berapa banyak tugas yang dapat menunggu eksekusi. Antrean tak terbatas (LinkedBlockingQueue tanpa batas) dapat menyebabkan OOM saat tugas datang dengan cepat. Antrean terbatas (ArrayBlockingQueue dengan ukuran tetap) menolak tugas saat meluap. Untuk aplikasi mobile, ArrayBlockingQueue dengan kapasitas 16-32 tugas direkomendasikan. CallerRunsPolicy — RejectedExecutionHandler terbaik untuk perangkat mobile: memperlambat pengirim (pressure back) daripada kehilangan tugas.

ParameterRekomendasi untuk mobileAlasan
corePoolSize2-4Sumber daya terbatas perangkat mobile
maxPoolSizecorePoolSize (atau +1-2)Menghindari beban puncak pembuatan utas
keepAliveTime15-30 detikPembebasan memori cepat, tanpa sering membuat
Queue capacity16-32Keseimbangan antara buffering dan risiko OOM
HandlerCallerRunsPolicyTekanan balik tanpa kehilangan tugas

Kesalahan umum saat bekerja dengan kumpulan utas

Pengembang aplikasi mobile sering membuat kesalahan saat menggunakan kumpulan utas, yang menyebabkan crash, kebocoran memori dan operasi yang tidak stabil. Paling umum: tidak memanggil shutdown() untuk ExecutorService, membuat pool baru untuk setiap operasi, pool terlalu besar, deadlock antar tugas, menggunakan CachedThreadPool di Android.

Deadlock di Thread Pool

Deadlock terjadi ketika suatu tugas di pool menunggu hasil tugas lain dari pool yang sama, tetapi semua utas sibuk menunggu. Contoh: tugas A mengirim tugas B ke pool yang sama dan memanggil future.get() — jika pool habis, tugas A menunggu tugas B, dan tugas B tidak dapat dijalankan karena tidak ada utas kosong. Solusi: gunakan pool terpisah untuk tingkat tugas yang berbeda atau callback asinkron daripada .get() yang memblokir.

kotlin
// Deadlock di Thread Pool
val pool = Executors.newFixedThreadPool(1)

// Tugas A menunggu tugas B — deadlock!
val futureA = pool.submit {
    // Tugas ini tidak akan pernah dijalankan
    val futureB = pool.submit { 42 }
    futureB.get() // Diblokir selamanya
}

// Perbaikan: pool terpisah
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()

workerPool.submit {
    callbackPool.submit {
        // Dijalankan di pool terpisah — deadlock tidak mungkin
    }
}

// Atau gunakan CompletableFuture
workerPool.submit {
    CompletableFuture
        .supplyAsync { 42 }
        .thenAccept { result ->
            println(result)
        }
}

Pool yang tidak selesai dan kebocoran

ExecutorService yang dibuat di Activity harus diakhiri di onDestroy(). Jika tidak dilakukan, utas akan tetap berada di memori bahkan setelah Activity dihancurkan. Solusi: simpan pool dalam lingkup Application atau ViewModel, bukan di Activity. Untuk coroutine, gunakan viewModelScope atau lifecycleScope. Jika pool dibuat di dalam Activity, pastikan untuk memanggil pool.shutdown() di onDestroy(). Untuk pengujian, gunakan es.shutdownNow() untuk penghentian segera.

Pertanyaan yang sering diajukan

Apa perbedaan Thread Pool dengan utas biasa?

Thread Pool menggunakan kembali utas yang sudah dibuat untuk menjalankan beberapa tugas. Utas biasa (raw Thread) dibuat, menjalankan satu tugas dan dihancurkan. Membuat utas membutuhkan sekitar 1 MB memori dan ~1 ms waktu. Thread Pool mengurangi biaya overhead, membatasi jumlah maksimum utas dan menyediakan API manajemen (shutdown, awaitTermination).

Berapa banyak utas yang harus ada di pool untuk aplikasi mobile?

Untuk aplikasi mobile tipikal, 2-4 utas adalah optimal. Untuk tugas CPU-bound — jumlah inti CPU. Untuk tugas IO-bound — jumlah inti × 2. Lebih banyak utas meningkatkan konsumsi daya dan peralihan konteks tanpa peningkatan kinerja. Di Android, gunakan Process.availableProcessors() untuk menentukan inti. Di iOS — ProcessInfo.processInfo.processorCount.

Apa itu CachedThreadPool dan mengapa berbahaya di Android?

CachedThreadPool membuat utas sesuai kebutuhan dan menggunakan kembali utas yang ada. Masalah: tidak membatasi jumlah maksimum utas. Jika 100 tugas datang bersamaan, 100 utas akan dibuat. Ini menyebabkan OOM di Android (setiap utas ~1 MB). Gunakan newFixedThreadPool(n) dengan batas eksplisit. CachedThreadPool hanya diizinkan untuk tugas burst jangka pendek dengan jaminan volume kecil.

Apakah perlu memanggil shutdown() untuk ExecutorService?

Ya, jika pool bukan milik wadah terkelola (seperti coroutine). shutdown() menghentikan penerimaan tugas baru dan mengakhiri utas setelah menyelesaikan tugas saat ini. Tanpa shutdown(), utas tetap di memori dan aplikasi tidak berhenti. Untuk Activity, panggil di onDestroy(). Untuk ViewModel, gunakan coroutineScope. Mengakhiri pool adalah bagian wajib dari manajemen sumber daya, mirip dengan menutup Cursor atau InputStream.

Apakah OperationQueue dan DispatchQueue adalah kumpulan utas?

Ya, OperationQueue dan DispatchQueue adalah kumpulan utas yang disediakan oleh iOS. OperationQueue membatasi konkuransi melalui maxConcurrentOperationCount. DispatchQueue.global() menggunakan kumpulan utas sistem tanpa kontrol langsung. Tidak seperti Java ThreadPoolExecutor, Anda tidak mengelola corePoolSize atau queue capacity — sistem mengoptimalkan pool secara otomatis di bawah beban saat ini dan konsumsi daya perangkat.

Ringkasan

  • Thread Pool — kumpulan utas yang dapat digunakan kembali untuk menjalankan tugas latar belakang, mengurangi biaya overhead pembuatan utas.
  • Android menggunakan ThreadPoolExecutor dan Executors.newFixedThreadPool(n) dengan batasan ukuran pool yang eksplisit.
  • iOS menyediakan OperationQueue dengan maxConcurrentOperationCount dan GCD DispatchQueue dengan pool QoS.
  • Core pool size — jumlah minimum utas; maximum pool size — maksimum saat antrean meluap.
  • Deadlock di pool terjadi ketika suatu tugas menunggu tugas lain dari pool yang sama.
  • CallerRunsPolicy lebih disukai untuk aplikasi mobile — memperlambat pengirim tanpa kehilangan tugas.
  • Ukuran pool optimal untuk aplikasi mobile tipikal adalah 2-4 utas dengan penutupan melalui shutdown().

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