Thread Pool — ito ay isang mekanismo ng pamamahala ng thread kung saan ang isang pre-created na pool ng mga thread ay muling ginagamit upang magsagawa ng mga gawain, iniiwasan ang overhead ng paglikha at pagsira ng mga thread. Sa mobile development, ang thread pool ay ginagamit para sa mga background operation: mga network request, pagproseso ng imahe, pagtatrabaho sa mga database. Ayon sa Google Android Documentation (2025), ang ExecutorService ay ang inirerekomendang paraan ng pamamahala ng mga background thread sa Android. Sa iOS, ang katulad na papel ay ginagampanan ng OperationQueue at GCD DispatchQueue na may mga global concurrent queue.
Mga pangunahing punto
Thread Pool (pool ng thread) — ay isang architectural pattern kung saan ang isang nakapirming bilang ng mga thread ay ginagawa nang maaga at muling ginagamit upang magsagawa ng maraming gawain. Sa halip na lumikha ng bagong thread para sa bawat operasyon (na mahal: mga 1 MB ng stack bawat thread sa JVM), ang mga gawain ay inilalagay sa isang queue at isinasagawa ng mga libreng thread mula sa pool. Sa mobile development, ang thread pool ay kritikal para sa pagganap — nililimitahan ng Android at iOS ang bilang ng mga thread bawat app.
Ang paglikha ng thread ay isang mahal na operasyon: paglalaan ng stack, pagpaparehistro sa system, paglipat ng konteksto. Sa mga mobile device na may limitadong resources, ang hindi kontroladong paglikha ng thread ay humahantong sa OOM (OutOfMemoryError) sa Android at throttling sa iOS. Thread Pool ay lumulutas ng parehong problema: nililimitahan ang maximum na bilang ng sabay na tumatakbong thread at muling ginagamit ang mga nagawa nang thread. Inirerekomenda ng Google ang ExecutorService sa halip na raw Thread(), inirerekomenda ng Apple ang OperationQueue sa halip na Thread.
| Parameter | Walang pool (raw Thread) | May Thread Pool |
|---|---|---|
| Paglikha ng thread | Para sa bawat gawain | Isang beses sa paggawa ng pool |
| Maximum na thread | Walang limitasyon (peligro ng OOM) | Nilimitahan ng core/max pool size |
| Paggamit | Mababa (thread namamatay pagkatapos ng gawain) | Mataas (thread ay muling ginagamit) |
| Pamamahala | Manual (join, interrupt) | Awtomatiko (ExecutorService) |
| Pagkonsumo ng memorya | Tumataas sa bawat gawain | Nakapirmi |
Ang thread pool ay gumagana sa prinsipyong Producer-Consumer: ang mga gawain (Runnable/Callable) ay inilalagay sa isang naka-block na queue (BlockingQueue). Ang mga thread mula sa pool ay naghihintay ng mga gawain sa queue at kinukuha ang mga ito para isagawa. Algoritmo: kung mas kaunti ang libreng thread kaysa corePoolSize, isang bagong thread ang ginagawa. Kung naabot na ang corePoolSize, ang gawain ay inilalagay sa queue. Kung puno ang queue at mas kaunti ang thread kaysa maximumPoolSize, isang karagdagang thread ang ginagawa. Kapag nalampasan ang maximumPoolSize, ang gawain ay tinatanggihan sa pamamagitan ng RejectedExecutionHandler.
Core pool size — ang bilang ng mga thread na pinananatili sa pool kahit na idle. Maximum pool size — ang maximum na bilang ng mga thread na maaaring gawin kapag umapaw ang queue. Ang pagkakaiba sa pagitan ng mga ito ay mga karagdagang (overflow) thread na ginagawa pansamantala at tinatapos pagkatapos ng idle timeout. Sa mga mobile device, inirerekomenda na itakda ang corePoolSize na katumbas ng maximumPoolSize upang maiwasan ang peak load sa paggawa ng thread.
BlockingQueue ay nag-iimbak ng mga gawaing naghihintay ng execution. Ang pinakasikat na implementasyon: LinkedBlockingQueue (walang limitasyon), ArrayBlockingQueue (may limitasyon) at SynchronousQueue (walang storage — ang gawain ay agad na ipinapasa sa thread). Kapag umapaw ang queue at pool, RejectedExecutionHandler ay isinaaktibo. Mga karaniwang patakaran: AbortPolicy (nagtatapon ng RejectedExecutionException), CallerRunsPolicy (nagsasagawa sa thread ng nagpadala), DiscardPolicy at DiscardOldestPolicy.
// Paglikha ng Thread Pool sa Android
val threadPool = ThreadPoolExecutor(
corePoolSize = 2, // Minimum na 2 thread
maximumPoolSize = 4, // Maximum na 4 thread
keepAliveTime = 30L, // Buhay ng overflow thread
unit = TimeUnit.SECONDS,
workQueue = LinkedBlockingQueue<Runnable>(16),
threadFactory = Executors.defaultThreadFactory(),
handler = ThreadPoolExecutor.CallerRunsPolicy()
)
// Pagpapadala ng mga gawain
threadPool.execute {
val result = api.fetchData()
runOnUiThread { showData(result) }
}
// Pagsasara ng pool
threadPool.shutdown()
// Pag-antay sa pagkumpleto ng lahat ng gawain
threadPool.awaitTermination(10, TimeUnit.SECONDS)
Ang Android ay nagbibigay ng ilang implementasyon ng thread pool sa pamamagitan ng java.util.concurrent. Executors — isang pabrika na may mga handa nang configuration: newFixedThreadPool(n) (fixed pool), newCachedThreadPool() (walang limitasyon, ang mga thread ay ginagawa kung kinakailangan), newSingleThreadExecutor() (isang thread — sequential execution). Para sa mga mobile project, inirerekomenda ang newFixedThreadPool na may makatwirang limitasyon (2-4 thread), dahil ang cached pool ay maaaring lumikha ng masyadong maraming thread.
ThreadPoolExecutor (TPE) — ang kumpletong implementasyon ng ExecutorService na may mga configurable na parameter. Sa Android, ang TPE ay ginagamit sa loob ng AsyncTask, IntentService at JobIntentService. Ang mga parameter na corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue at RejectedExecutionHandler ay nagpapahintulot ng fine-tuning ng pag-uugali ng pool. Mga rekomendasyon para sa Android: corePoolSize = bilang ng CPU cores - 1 (para sa IO-bound tasks) o bilang ng cores (para sa CPU-bound tasks). Para sa mga tipikal na app — 2-4 thread.
// Mga handa na configuration ng Executors
// 1. Fixed pool para sa 3 thread
val fixedPool = Executors.newFixedThreadPool(3)
// 2. Naka-cache na pool (hindi inirerekomenda para sa mobile)
val cachedPool = Executors.newCachedThreadPool()
// 3. Nag-iisang thread (serialization)
val singlePool = Executors.newSingleThreadExecutor()
// 4. Scheduler (pana-panahong mga gawain)
val scheduler = Executors.newScheduledThreadPool(2)
// Paggamit sa Callable at Future
val future: Future<String> = fixedPool.submit(Callable {
"Result: ${api.call()}"
})
// Pagkuha ng resulta (binablock ang thread)
val result = future.get(5, TimeUnit.SECONDS)
// Pagsasara ng pool
fixedPool.shutdownNow()
Ang Kotlin coroutine ay nagbibigay ng CoroutineDispatcher — isang abstraction na katulad ng thread pool. Ang Dispatchers.IO ay gumagamit ng pool ng 64 thread (limitado). Ang Dispatchers.Default — pool na katumbas ng bilang ng CPU cores. Ang CoroutineDispatcher ay hindi nangangailangan ng manual shutdown at awtomatikong pinamamahalaan. Para sa fine-tuning, gumawa ng sarili mong ExecutorCoroutineDispatcher sa pamamagitan ng Executors.newFixedThreadPool(2).asCoroutineDispatcher(). Ang mga coroutine ay hindi pumapalit sa thread pool, kundi binabalot ito.
Ang iOS ay nagbibigay ng dalawang pangunahing mekanismo para sa pamamahala ng thread pool: OperationQueue (high-level API batay sa GCD) at GCD DispatchQueue (low-level C-API). Ang OperationQueue ay nag-eencapsulate ng thread pool sa pamamagitan ng property na maxConcurrentOperationCount. Bilang default, ang OperationQueue ay gumagamit ng system-defined maximum (depende sa load ng system). Ang DispatchQueue.global() ay nagbibigay ng concurrent queue na may system thread pool.
OperationQueue ay namamahala ng thread pool sa pamamagitan ng maxConcurrentOperationCount. Ang value na 1 ay gumagawa ng serial queue (katulad ng single thread pool). Ang value na higit sa 1 — concurrent pool na may tinukoy na limitasyon. Bilang default, maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (system optimum, karaniwang 4-8 thread). Ang Operation ay sumusuporta sa mga dependency, priority at pagkansela. Ang bawat operasyon ay isinasagawa sa anumang libreng thread mula sa system pool.
// OperationQueue na may pool ng 3 thread
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility
// Paglikha ng mga operasyon
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) }
}
// Dependency: operation2 naghihintay sa operation1
operation2.addDependency(operation1)
// Pagdagdag sa queue
queue.addOperations([operation1, operation2], waitUntilFinished: false)
// Pagkansela ng lahat ng operasyon
queue.cancelAllOperations()
DispatchQueue — ang thread pool mula sa Apple. Ang concurrent queue (qos: .utility) ay gumagamit ng system thread pool, na optimize para sa kasalukuyang load ng device. Ang iba't ibang QoS (userInteractive, userInitiated, utility, background) ay na-map sa iba't ibang pool na may iba't ibang priority. DispatchGroup ay nagpapahintulot ng synchronization ng maraming gawain. Ang DispatchWorkItem ay sumusuporta sa pagkansela at qualityOfService. Para sa fine control, gumawa ng sarili mong concurrent queue sa pamamagitan ng DispatchQueue(label: qos: attributes: .concurrent).
// GCD DispatchQueue bilang thread pool
let customQueue = DispatchQueue(
label: "com.app.background",
qos: .utility,
attributes: .concurrent,
autoreleaseFrequency: .workItem
)
// Pagpapadala ng mga gawain sa pool
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }
// DispatchGroup para sa synchronization
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() // Parehong gawain ay natapos
}
// Paglimit ng concurrency sa pamamagitan ng semaphore
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
pool.async {
semaphore.wait()
download(url)
semaphore.signal()
}
}
Ang configuration ng thread pool ay direktang nakakaapekto sa pagganap ng app. Mga maling parameter ay humahantong sa hindi sapat na paggamit ng CPU (sobrang kaunting thread) o overload ng system (sobrang daming thread). Sa mga mobile app, ang optimal na mga value ay naiiba sa server values dahil sa limitadong resources at pagkonsumo ng kuryente. Mga pangunahing parameter: corePoolSize, maxPoolSize, queue capacity at keepAliveTime.
Formula para sa IO-bound tasks: corePoolSize = bilang ng CPU cores × 2 (naghihintay ang mga thread ng input-output). Para sa CPU-bound tasks: corePoolSize = bilang ng CPU cores (ang mga thread ay palaging abala sa pag-compute). Sa modernong mobile device (6-8 cores), ito ay nagbibigay ng 6-8 thread para sa CPU-bound at 12-16 para sa IO-bound. Ipinapakita ng practical na mga test na para sa tipikal na mobile app, 3-4 thread ay optimal — mas maraming thread ay nagpapataas ng pagkonsumo ng kuryente nang walang pagtaas ng pagganap.
Ang laki ng task queue (work queue) ay tumutukoy kung ilang gawain ang maaaring maghintay ng execution. Walang limitasyong queue (LinkedBlockingQueue na walang limitasyon) ay maaaring humantong sa OOM sa mabilis na pagdating ng mga gawain. Limitadong queue (ArrayBlockingQueue na may nakapirming laki) ay tumatanggi sa mga gawain sa pag-apaw. Para sa mga mobile app, inirerekomenda ang ArrayBlockingQueue na may kapasidad na 16-32 gawain. CallerRunsPolicy — ang pinakamahusay na RejectedExecutionHandler para sa mga mobile device: pinapabagal ang nagpadala (pressure back) sa halip na mawala ang gawain.
| Parameter | Rekomendasyon para sa mobile | Dahilan |
|---|---|---|
| corePoolSize | 2-4 | Limitadong resources ng mobile device |
| maxPoolSize | corePoolSize (o +1-2) | Iwasan ang peak load sa paggawa ng thread |
| keepAliveTime | 15-30 segundo | Mabilis na pagpapalaya ng memorya, ngunit walang madalas na paggawa |
| Queue capacity | 16-32 | Balanse sa pagitan ng buffering at peligro ng OOM |
| Handler | CallerRunsPolicy | Backpressure nang walang pagkawala ng gawain |
Ang mga developer ng mobile app ay madalas na nagkakamali sa paggamit ng thread pool, na humahantong sa pag-crash, pagtagas ng memorya at hindi matatag na operasyon. Pinakakaraniwan: hindi pagtawag ng shutdown() para sa ExecutorService, paggawa ng bagong pool para sa bawat operasyon, masyadong malaking pool, deadlock sa pagitan ng mga gawain, paggamit ng CachedThreadPool sa Android.
Ang deadlock ay nangyayari kapag ang isang gawain sa pool ay naghihintay ng resulta ng isa pang gawain mula sa parehong pool, ngunit lahat ng thread ay abala sa paghihintay. Halimbawa: ang gawain A ay nagpapadala ng gawain B sa parehong pool at tumatawag ng future.get() — kung ang pool ay naubos, ang gawain A ay naghihintay ng gawain B, at ang gawain B ay hindi maisasagawa dahil walang libreng thread. Solusyon: gumamit ng hiwalay na pool para sa iba't ibang antas ng gawain o async callback sa halip na .get() na humaharang.
// Deadlock sa Thread Pool
val pool = Executors.newFixedThreadPool(1)
// Gawain A naghihintay sa gawain B — deadlock!
val futureA = pool.submit {
// Ang gawaing ito ay hindi kailanman maisasagawa
val futureB = pool.submit { 42 }
futureB.get() // Naka-block magpakailanman
}
// Pag-aayos: hiwalay na mga pool
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()
workerPool.submit {
callbackPool.submit {
// Isinasagawa sa hiwalay na pool — deadlock imposible
}
}
// O gumamit ng CompletableFuture
workerPool.submit {
CompletableFuture
.supplyAsync { 42 }
.thenAccept { result ->
println(result)
}
}
Ang ExecutorService na ginawa sa Activity ay dapat tapusin sa onDestroy(). Kung hindi ito gagawin, ang mga thread ay mananatili sa memorya kahit na pagkatapos ng pagkasira ng Activity. Solusyon: itago ang pool sa Application scope o ViewModel, hindi sa Activity. Para sa coroutine, gumamit ng viewModelScope o lifecycleScope. Kung ang pool ay ginawa sa loob ng Activity, tiyaking tawagan ang pool.shutdown() sa onDestroy(). Para sa pagsubok, gumamit ng es.shutdownNow() para sa agarang paghinto.
Mga madalas itanong
Thread Pool ay muling ginagamit ang mga nagawa nang thread para magsagawa ng maraming gawain. Ang ordinaryong thread (raw Thread) ay ginagawa, nagsasagawa ng isang gawain at sinisira. Ang paggawa ng thread ay tumatagal ng mga 1 MB memorya at ~1 ms oras. Binabawasan ng Thread Pool ang overhead, nililimitahan ang maximum na bilang ng thread at nagbibigay ng management API (shutdown, awaitTermination).
Para sa tipikal na mobile app, 2-4 thread ay optimal. Para sa CPU-bound tasks — bilang ng CPU cores. Para sa IO-bound tasks — bilang ng cores × 2. Mas maraming thread ay nagpapataas ng pagkonsumo ng kuryente at paglipat ng konteksto nang walang pagtaas ng pagganap. Sa Android, gamitin ang Process.availableProcessors() para matukoy ang cores. Sa iOS — ProcessInfo.processInfo.processorCount.
CachedThreadPool ay gumagawa ng thread kung kinakailangan at muling ginagamit ang mga umiiral na thread. Problema: hindi nito nililimitahan ang maximum na bilang ng thread. Kung 100 gawain ang dumating nang sabay, 100 thread ang gagawin. Ito ay humahantong sa OOM sa Android (bawat thread ~1 MB). Gumamit ng newFixedThreadPool(n) na may explicit na limitasyon. Ang CachedThreadPool ay pinapayagan lamang para sa panandaliang burst task na may garantiya ng maliit na volume.
Oo, kung ang pool ay hindi kabilang sa isang managed container (tulad ng coroutine). Ang shutdown() ay humihinto sa pagtanggap ng mga bagong gawain at tinatapos ang mga thread pagkatapos makumpleto ang kasalukuyang gawain. Kung walang shutdown(), ang mga thread ay mananatili sa memorya at ang app ay hindi magtatapos. Para sa Activity, tawagan sa onDestroy(). Para sa ViewModel, gumamit ng coroutineScope. Ang pagtatapos ng pool ay isang mandatoryong bahagi ng pamamahala ng resources, katulad ng pagsasara ng Cursor o InputStream.
Oo, ang OperationQueue at DispatchQueue ay thread pool na ibinigay ng iOS. Nililimitahan ng OperationQueue ang concurrency sa pamamagitan ng maxConcurrentOperationCount. Ang DispatchQueue.global() ay gumagamit ng system thread pool nang walang direktang kontrol. Hindi tulad ng Java ThreadPoolExecutor, hindi mo pinamamahalaan ang corePoolSize o queue capacity — awtomatikong ini-optimize ng system ang pool sa ilalim ng kasalukuyang load at pagkonsumo ng kuryente ng device.
Buod
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