Thread Pool — önceden oluşturulmuş bir iş parçacığı havuzunun görevleri yürütmek için yeniden kullanıldığı, iş parçacığı oluşturma ve yok etme yükünü ortadan kaldıran bir iş parçacığı yönetim mekanizmasıdır. Mobil geliştirmede, iş parçacığı havuzu arka plan işlemleri için kullanılır: ağ istekleri, görüntü işleme, veritabanı işlemleri. Google Android Dokümantasyonu'na (2025) göre ExecutorService, Android'de arka plan iş parçacıklarını yönetmek için önerilen yöntemdir. iOS'ta OperationQueue ve genel concurrent kuyruklara sahip GCD DispatchQueue benzer bir rol oynar.
Önemli noktalar
Thread Pool (iş parçacığı havuzu) — belirli sayıda iş parçacığının önceden oluşturulduğu ve birden çok görevi yürütmek için yeniden kullanıldığı bir mimari desendir. Her işlem için yeni bir iş parçacığı oluşturmak yerine (ki bu pahalıdır: JVM'de iş parçacığı başına yaklaşık 1 MB yığın), görevler bir kuyruğa yerleştirilir ve havuzdaki mevcut iş parçacıkları tarafından yürütülür. Mobil geliştirmede, iş parçacığı havuzu performans için kritiktir — Android ve iOS uygulama başına iş parçacığı sayısını sınırlar.
İş parçacığı oluşturmak pahalı bir işlemdir: yığın ayırma, sistem kaydı, bağlam değiştirme. Sınırlı kaynaklara sahip mobil cihazlarda, kontrolsüz iş parçacığı oluşturma Android'de OOM'ye (OutOfMemoryError) ve iOS'ta kısıtlamaya yol açar. Thread Pool her iki sorunu da çözer: aynı anda çalışan maksimum iş parçacığı sayısını sınırlar ve önceden oluşturulmuş iş parçacıklarını yeniden kullanır. Google raw Thread() yerine ExecutorService, Apple Thread yerine OperationQueue önerir.
| Parametre | Havuzsuz (raw Thread) | Thread Pool ile |
|---|---|---|
| İş parçacığı oluşturma | Her görev için | Havuz oluştururken bir kez |
| Maksimum iş parçacığı | Sınırsız (OOM riski) | core/max pool size ile sınırlı |
| Kullanım | Düşük (görevden sonra iş parçacığı ölür) | Yüksek (iş parçacığı yeniden kullanılır) |
| Yönetim | Manuel (join, interrupt) | Otomatik (ExecutorService) |
| Bellek tüketimi | Her görevle artar | Sabit |
İş parçacığı havuzu Üretici-Tüketici prensibiyle çalışır: görevler (Runnable/Callable) bir engelleme kuyruğuna (BlockingQueue) yerleştirilir. Havuzdaki iş parçacıkları kuyrukta görev bekler ve yürütmek için alır. Algoritma: boş iş parçacığı sayısı corePoolSize'dan azsa yeni bir iş parçacığı oluşturulur. corePoolSize'a ulaşılırsa görev kuyruğa yerleştirilir. Kuyruk doluysa ve iş parçacığı sayısı maximumPoolSize'dan azsa ek bir iş parçacığı oluşturulur. maximumPoolSize aşılırsa görev RejectedExecutionHandler aracılığıyla reddedilir.
Core pool size — boştayken bile havuzda tutulan iş parçacığı sayısı. Maximum pool size — kuyruk taştığında oluşturulabilecek maksimum iş parçacığı sayısı. Aralarındaki fark, geçici olarak oluşturulan ve boşta kalma zaman aşımından sonra sonlandırılan ek (overflow) iş parçacıklarıdır. Mobil cihazlarda, iş parçacığı oluşturmadan kaynaklanan tepe yüklerden kaçınmak için corePoolSize'ın maximumPoolSize'a eşit ayarlanması önerilir.
BlockingQueue yürütülmeyi bekleyen görevleri depolar. En popüler uygulamalar: LinkedBlockingQueue (sınırsız), ArrayBlockingQueue (sınırlı) ve SynchronousQueue (depolama yok — görev doğrudan bir iş parçacığına iletilir). Kuyruk ve havuz dolduğunda RejectedExecutionHandler tetiklenir. Standart politikalar: AbortPolicy (RejectedExecutionException fırlatır), CallerRunsPolicy (gönderenin iş parçacığında yürütür), DiscardPolicy ve DiscardOldestPolicy.
// Android'de Thread Pool oluşturma
val threadPool = ThreadPoolExecutor(
corePoolSize = 2, // Minimum 2 iş parçacığı
maximumPoolSize = 4, // Maksimum 4 iş parçacığı
keepAliveTime = 30L, // Taşma iş parçacığının yaşam süresi
unit = TimeUnit.SECONDS,
workQueue = LinkedBlockingQueue<Runnable>(16),
threadFactory = Executors.defaultThreadFactory(),
handler = ThreadPoolExecutor.CallerRunsPolicy()
)
// Görev gönderme
threadPool.execute {
val result = api.fetchData()
runOnUiThread { showData(result) }
}
// Havuzu kapatma
threadPool.shutdown()
// Tüm görevlerin tamamlanmasını bekleme
threadPool.awaitTermination(10, TimeUnit.SECONDS)
Android, java.util.concurrent aracılığıyla birden çok iş parçacığı havuzu uygulaması sağlar. Executors — hazır yapılandırmalara sahip bir fabrika: newFixedThreadPool(n) (sabit havuz), newCachedThreadPool() (sınırsız, ihtiyaç duyuldukça iş parçacığı oluşturulur), newSingleThreadExecutor() (tek iş parçacığı — sıralı yürütme). Mobil projeler için, makul bir sınırla (2-4 iş parçacığı) newFixedThreadPool önerilir, çünkü önbelleğe alınmış havuz çok fazla iş parçacığı oluşturabilir.
ThreadPoolExecutor (TPE) — yapılandırılabilir parametrelere sahip ExecutorService'in tam uygulaması. Android'de TPE, AsyncTask, IntentService ve JobIntentService'in içinde kullanılır. corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue ve RejectedExecutionHandler parametreleri, havuz davranışının ince ayarını yapmaya olanak tanır. Android için öneriler: corePoolSize = CPU çekirdek sayısı - 1 (IO-bound görevler için) veya çekirdek sayısı (CPU-bound görevler için). Tipik uygulamalar için: 2-4 iş parçacığı.
// Executors hazır yapılandırmaları
// 1. 3 iş parçacıklı sabit havuz
val fixedPool = Executors.newFixedThreadPool(3)
// 2. Önbelleğe alınmış havuz (mobil için önerilmez)
val cachedPool = Executors.newCachedThreadPool()
// 3. Tek iş parçacığı (serileştirme)
val singlePool = Executors.newSingleThreadExecutor()
// 4. Zamanlayıcı (periyodik görevler)
val scheduler = Executors.newScheduledThreadPool(2)
// Callable ve Future ile kullanım
val future: Future<String> = fixedPool.submit(Callable {
"Result: ${api.call()}"
})
// Sonucu alma (iş parçacığını bloke eder)
val result = future.get(5, TimeUnit.SECONDS)
// Havuzu kapatma
fixedPool.shutdownNow()
Kotlin coroutine'leri CoroutineDispatcher sağlar — iş parçacığı havuzuna benzer bir soyutlama. Dispatchers.IO, 64 iş parçacığından (sınırlı) oluşan bir havuz kullanır. Dispatchers.Default — CPU çekirdek sayısına eşit bir havuz. CoroutineDispatcher manuel kapatma gerektirmez ve otomatik olarak yönetilir. İnce ayar için, Executors.newFixedThreadPool(2).asCoroutineDispatcher() aracılığıyla özel bir ExecutorCoroutineDispatcher oluşturun. Coroutine'ler iş parçacığı havuzunun yerini almaz, onu sarar.
iOS, iş parçacığı havuzu yönetimi için iki ana mekanizma sağlar: OperationQueue (GCD üzerine inşa edilmiş üst düzey API) ve GCD DispatchQueue (alt düzey C-API). OperationQueue, maxConcurrentOperationCount özelliği aracılığıyla iş parçacığı havuzunu kapsüller. Varsayılan olarak OperationQueue, sistem tarafından tanımlanan bir maksimum kullanır (sistem yüküne bağlıdır). DispatchQueue.global(), bir sistem iş parçacığı havuzuyla concurrent kuyruk sağlar.
OperationQueue, iş parçacığı havuzunu maxConcurrentOperationCount aracılığıyla yönetir. Değer 1, seri bir kuyruk oluşturur (tek iş parçacıklı havuza benzer). 1'den büyük bir değer, belirtilen sınırla concurrent bir havuz oluşturur. Varsayılan olarak, maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (sistem optimumu, genellikle 4-8 iş parçacığı). Operation, bağımlılıkları, öncelikleri ve iptali destekler. Her işlem, sistem havuzundaki mevcut herhangi bir iş parçacığında yürütülür.
// 3 iş parçacıklı havuzla OperationQueue
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility
// İşlem oluşturma
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) }
}
// Bağımlılık: operation2, operation1'i bekler
operation2.addDependency(operation1)
// Kuyruğa ekleme
queue.addOperations([operation1, operation2], waitUntilFinished: false)
// Tüm işlemleri iptal etme
queue.cancelAllOperations()
DispatchQueue — Apple'ın iş parçacığı havuzudur. Bir concurrent kuyruk (qos: .utility), geçerli cihaz yükü için optimize edilmiş sistem iş parçacığı havuzunu kullanır. Farklı QoS seviyeleri (userInteractive, userInitiated, utility, background), farklı önceliklere sahip farklı havuzlara eşlenir. DispatchGroup birden çok görevi senkronize etmeye olanak tanır. DispatchWorkItem, iptal ve qualityOfService'i destekler. İnce kontrol için, DispatchQueue(label: qos: attributes: .concurrent) aracılığıyla özel concurrent kuyruklar oluşturun.
// GCD DispatchQueue iş parçacığı havuzu olarak
let customQueue = DispatchQueue(
label: "com.app.background",
qos: .utility,
attributes: .concurrent,
autoreleaseFrequency: .workItem
)
// Havuza görev gönderme
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }
// Senkronizasyon için DispatchGroup
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() // Her iki görev de tamamlandı
}
// Semaphore ile eşzamanlılığı sınırlama
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
pool.async {
semaphore.wait()
download(url)
semaphore.signal()
}
}
İş parçacığı havuzu yapılandırması, uygulama performansını doğrudan etkiler. Yanlış parametreler, CPU'nun yetersiz kullanımına (çok az iş parçacığı) veya sistem aşırı yüklenmesine (çok fazla) yol açar. Mobil uygulamalar için optimum değerler, sınırlı kaynaklar ve güç tüketimi nedeniyle sunucu tarafından farklılık gösterir. Ana parametreler şunlardır: corePoolSize, maxPoolSize, kuyruk kapasitesi ve keepAliveTime.
IO-bound görevler için formül: corePoolSize = CPU çekirdek sayısı × 2 (iş parçacıkları G/Ç bekler). CPU-bound görevler için: corePoolSize = CPU çekirdek sayısı (iş parçacıkları sürekli hesaplamalarla meşgul). Modern mobil cihazlarda (6-8 çekirdek) bu, CPU-bound için 6-8 iş parçacığı ve IO-bound için 12-16 iş parçacığı verir. Pratik testler, tipik bir mobil uygulama için 3-4 iş parçacığının optimum olduğunu gösterir — daha fazla iş parçacığı, performans artışı olmadan güç tüketimini artırır.
İş kuyruğunun (work queue) boyutu, kaç görevin yürütmeyi bekleyebileceğini belirler. Sınırsız kuyruk (LinkedBlockingQueue sınırsız), hızlı görev gelişinde OOM'ye yol açabilir. Sınırlı kuyruk (ArrayBlockingQueue sabit boyutlu), dolduğunda görevleri reddeder. Mobil uygulamalar için 16-32 görev kapasiteli ArrayBlockingQueue önerilir. CallerRunsPolicy, mobil için en iyi RejectedExecutionHandler'dır: görevi kaybetmek yerine göndereni yavaşlatır (geri basınç).
| Parametre | Mobil için öneri | Gerekçe |
|---|---|---|
| corePoolSize | 2-4 | Mobil cihazın sınırlı kaynakları |
| maxPoolSize | corePoolSize (veya +1-2) | İş parçacığı oluşturmadan kaynaklanan tepe yüklerden kaçınma |
| keepAliveTime | 15-30 saniye | Sık oluşturma olmadan hızlı bellek serbest bırakma |
| Kuyruk kapasitesi | 16-32 | Ara belleğe alma ve OOM riski arasında denge |
| Handler | CallerRunsPolicy | Görev kaybı olmadan geri basınç |
Mobil uygulama geliştiricileri, iş parçacığı havuzunu kullanırken sıklıkla çökmelere, bellek sızıntılarına ve dengesiz çalışmaya yol açan hatalar yapar. En yaygın olanlar: ExecutorService için shutdown() çağırmamak, her işlem için yeni havuz oluşturmak, çok büyük havuz, görevler arasında deadlock, Android'de CachedThreadPool kullanmak.
Deadlock, havuzdaki bir görevin aynı havuzdaki başka bir görevin sonucunu beklemesi ancak tüm iş parçacıklarının beklemeyle meşgul olması durumunda oluşur. Örnek: görev A, aynı havuza görev B'yi gönderir ve future.get() çağırır — havuz tükenmişse, görev A görev B'yi bekler ve boş iş parçacığı olmadığı için görev B yürütülemez. Çözüm: farklı görev seviyeleri için ayrı havuzlar kullanın veya bloke edici .get() yerine async callback kullanın.
// Thread Pool'da Deadlock
val pool = Executors.newFixedThreadPool(1)
// Görev A, görev B'yi bekliyor — deadlock!
val futureA = pool.submit {
// Bu görev asla yürütülmeyecek
val futureB = pool.submit { 42 }
futureB.get() // Sonsuza kadar bloke
}
// Düzeltme: ayrı havuzlar
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()
workerPool.submit {
callbackPool.submit {
// Ayrı havuzda çalışıyor — deadlock imkansız
}
}
// Veya CompletableFuture kullanın
workerPool.submit {
CompletableFuture
.supplyAsync { 42 }
.thenAccept { result ->
println(result)
}
}
Bir Activity'de oluşturulan ExecutorService, onDestroy()'de kapatılmalıdır. Bu yapılmazsa, Activity yok edildikten sonra bile iş parçacıkları bellekte asılı kalır. Çözüm: havuzu Activity yerine Application kapsamında veya ViewModel'de tutun. Coroutine'ler için viewModelScope veya lifecycleScope kullanın. Havuz bir Activity içinde oluşturulmuşsa, onDestroy()'de pool.shutdown() çağırdığınızdan emin olun. Test için, anında durdurma için es.shutdownNow() kullanın.
Sıkça sorulan sorular
Thread Pool birden çok görevi yürütmek için önceden oluşturulmuş iş parçacıklarını yeniden kullanır. Normal bir iş parçacığı (raw Thread) oluşturulur, bir görevi yürütür ve yok edilir. Bir iş parçacığı oluşturmak yaklaşık 1 MB bellek ve ~1 ms zaman alır. Thread Pool yükü azaltır, maksimum iş parçacığı sayısını sınırlar ve yönetim için API (shutdown, awaitTermination) sağlar.
Tipik bir mobil uygulama için 2-4 iş parçacığı idealdir. CPU-bound görevler için — CPU çekirdek sayısı. IO-bound görevler için — çekirdek sayısı × 2. Daha fazla iş parçacığı, performans artışı olmadan güç tüketimini ve bağlam değiştirmeyi artırır. Android'de çekirdekleri belirlemek için Process.availableProcessors() kullanın. iOS'ta — ProcessInfo.processInfo.processorCount.
CachedThreadPool ihtiyaç duyuldukça iş parçacığı oluşturur ve mevcut olanları yeniden kullanır. Sorun: maksimum iş parçacığı sayısını sınırlamaz. Aynı anda 100 görev gelirse 100 iş parçacığı oluşturulur. Bu, Android'de OOM'ye yol açar (her iş parçacığı ~1 MB). Açık bir sınırla newFixedThreadPool(n) kullanın. CachedThreadPool yalnızca garantili küçük hacimli kısa süreli patlama görevleri için kabul edilebilir.
Evet, havuz yönetilen bir kapsayıcıya (coroutine'ler gibi) ait değilse. shutdown() yeni görevleri kabul etmeyi durdurur ve mevcut görevler tamamlandıktan sonra iş parçacıklarını sonlandırır. shutdown() olmadan iş parçacıkları bellekte asılı kalır ve uygulama sonlanmaz. Activity için onDestroy()'de çağırın. ViewModel için coroutineScope kullanın. Havuzu kapatmak, Cursor veya InputStream'i kapatmaya benzer şekilde kaynak yönetiminin zorunlu bir parçasıdır.
Evet, OperationQueue ve DispatchQueue iOS tarafından sağlanan iş parçacığı havuzlarıdır. OperationQueue, maxConcurrentOperationCount ile eşzamanlılığı sınırlar. DispatchQueue.global(), doğrudan kontrol olmadan sistem iş parçacığı havuzunu kullanır. Java ThreadPoolExecutor'un aksine, corePoolSize veya kuyruk kapasitesini yönetmezsiniz — sistem, geçerli yüke ve cihaz güç tüketimine göre havuzu otomatik olarak optimize eder.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun