Thread — основна јединица процесорског времена, која има сопствени стек и извршава се независно од других нити. У мобилном развоју, нити се користе за паралелно извршавање задатака како би интерфејс остао одзиван током дугих операција. Android подржава java.lang.Thread, Executors и Kotlin Coroutines, iOS — Thread (Objective-C), GCD и OperationQueue. Према Android Thread Documentation, креирање нативне нити захтева да оперативни систем издвоји ~1 MB за стек.
Кључно
Thread (нит извршавања) — независан низ инструкција који оперативни систем може да закаже на језгру CPU-ја. Сваки процес (апликација) садржи бар једну нит — Main Thread. Додатне нити се креирају за паралелно извршавање задатака. Свака нит има сопствени програмски стек (са локалним променљивим), програмски бројач (PC) и регистре. Меморија хипа (heap) је заједничка за све нити процеса.
У мобилним оперативним системима нити се планирају превентивним мултитаскингом (preemptive multitasking): оперативни систем може у сваком тренутку да прекине извршавање нити и преда контролу другој (context switch). Промена контекста је скупа операција (1–10 микросекунди), јер захтева чување/обнављање CPU регистара, ажурирање TLB-а и чишћење кешева. Управо зато превелик број нити (стотине и хиљаде) погоршава перформансе — оперативни систем троши више времена на пребацивање него на извршавање.
Нит и процес су различити појмови. Процес је инстанца апликације са издвојеном виртуелном меморијом. Нит унутар процеса дели ту меморију са другим нитима. У Android-у свака компонента апликације (Activity, Service, BroadcastReceiver) ради у једном процесу, али може да се извршава у различитим нитима. iOS апликација је такође један процес са могућношћу креирања додатних нити преко GCD-а или Thread-а.
Свака нит у Java/Kotlin (Android) и NSThread (iOS) пролази кроз пет стања: New (креирана), Runnable (спремна за извршавање), Running (извршава се на CPU-ју), Blocked/Waiting (чека ресурс или обавештење), Terminated (завршена). Прелазе између стања управља планер оперативног система и примитиви синхронизације. Програмер може да утиче на приоритет нити (Thread.setPriority()) и њено стање (sleep, join, interrupt).
У Android-у нит прелази у стање Blocked при покушају преузимања заузетог монитора (synchronized), позиву Object.wait() или Thread.sleep(). У iOS-у — при позиву NSCondition.wait(), pthread_cond_wait() или dispatch_semaphore_wait(). У стању Blocked нит не троши CPU, али заузима меморију (стек). Нит може бити прекинута (interrupted) из друге нити, добијајући InterruptedException (Java) или проверавајући isCancelled (Kotlin Coroutines).
| Стање | Опис | Метода прелаза |
|---|---|---|
| New | Нит је креирана, али није покренута | Thread() конструктор |
| Runnable | Нит је спремна за извршавање, чека CPU | thread.start() |
| Running | Нит се извршава на језгру CPU-ја | Планер оперативног система |
| Blocked/Waiting | Нит чека ресурс, монитор или обавештење | synchronized, wait(), sleep() |
| Terminated | Нит је завршила run() или је прекинута | run() завршен, interrupt() |
Context switch (промена контекста) — операција у којој оперативни систем чува стање тренутне нити (регистре, PC, TLB) и учитава сачувано стање друге. У мобилним системима (Linux + ART, XNU за iOS) context switch траје 1–10 микросекунди. Ако нит извршава задатак за 100 микросекунди, а context switch траје 5, онда се 5% времена губи. Да би се context switch минимизовао, iOS користи GCD са work stealing, а Android — пулове са fixedThreadCount.
Android је еволуирао од нискоризинског java.lang.Thread-а до савремених корутина. Сваки ниво апстракције даје више могућности уз мање додатне трошкове. Thread је основна класа, али њено директно креирање није препоручљиво: нова нит није под управом пула, тешко је пратити и отказати. AsyncTask (deprecated од API 30) је био корак напред, али је патио од цурења меморије и незгодне обраде конфигурација.
HandlerThread — посебна поткласа Thread-а са Looper-ом, која може да обрађује ред порука. Користи се за секвенцијално извршавање задатака у позадинској нити, на пример, уписивање података у Room или у датотеке. HandlerThread се креира позивом start(), након чега се кроз Handler(handlerThread.looper) могу слати поруке и Runnable. Позив handlerThread.quit() зауставља Looper и завршава нит.
// Android: Thread, HandlerThread и Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. Директно креирање Thread-а (није препоручљиво)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("Директна нит извршена")
})
thread.start()
}
// 2. HandlerThread за секвенцијалне позадинске задатке
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Секвенцијално извршавање у позадинској нити
Thread.sleep(500)
print("HandlerThread: задатак извршен")
}
// Заустављање нити (извршава се када су задаци завршени)
handlerThread.quitSafely()
}
// 3. Executors — пул нити
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 — савремени стандард
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
Пример ThreadExample приказује сва четири нивоа апстракције нити у Android-у. Директно креирање Thread-а је најнискоризинскији и најмање ефикасан приступ. HandlerThread је користан за секвенцијалне задатке у позадини. Executors.newFixedThreadPool(4) креира пул од 4 нити за паралелно извршавање до 10 задатака. Kotlin Coroutines са Dispatchers.Default — савремен, ефикасан и сигуран начин.
HandlerThread — специјализована поткласа Thread-а са уграђеним Looper-ом и редом порука. Креира се позивом start(), након чега се кроз Handler(handlerThread.looper) могу слати Runnable и поруке. HandlerThread извршава задатке строго секвенцијално — следећи задатак не почиње док се претходни не заврши. Ово је згодно за уписивање података у Room или датотеке, где је редослед операција критичан. Позив quitSafely() зауставља Looper након завршетка текућег задатка.
iOS такође пружа три нивоа рада са нитима. Thread (Thread у Swift-у, NSThread у Objective-C) — нискоризински API који директно креира нативну нит. GCD (Grand Central Dispatch) кроз DispatchQueue — основни алат за iOS програмере, који аутоматски управља пулом нити. OperationQueue — високоризинска апстракција изнад GCD-а са подршком за зависности, приоритете и отказивање.
Директна употреба Thread-а у савременом iOS развоју је изузетно ретка — GCD пружа све потребне могућности уз аутоматско управљање меморијом и нитима. Thread се користи само за специфичне случајеве: подешавање thread-local storage (threadDictionary), креирање RunLoop-а за позадинску нит или интеграција са C библиотекама које очекују pthread_t.
import Foundation
class ThreadManager {
// 1. Thread (нискоризински)
func createThread() {
let thread = Thread {
// Код се извршава на новој нити
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// Паралелни ред
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD async задатак")
}
// Barrier за синхронизацију уписивања
queue.async(flags: .barrier) {
// Ексклузивни приступ током уписивања
print("Barrier write: ексклузивни приступ")
}
}
// 3. OperationQueue са зависностима
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("Преузимање...")
}
let process = BlockOperation {
print("Обрада...")
}
let save = BlockOperation {
print("Чување...")
}
// Зависности: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// Thread-safe колекција кроз 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)
}
}
}
Класа ThreadSafeArray демонстрира образац Concurrent Read / Exclusive Write кроз GCD barrier. Читање кроз queue.sync{} извршава се паралелно из више нити. Уписивање кроз queue.async(flags: .barrier) блокира све друге операције (и читање и уписивање) до завршетка уписивања. Ово је ефикасније од synchronized блокова, јер не блокира читаоце док не постоји упис.
Директна употреба Thread-а у iOS-у је оправдана у три случаја: за thread-local storage (Thread.current.threadDictionary) — чување података везаних за нит; за креирање посебног RunLoop-а у позадинској нити са performSelector:onThread:; за интеграцију са C/C++ библиотекама које очекују pthread_t. У свим осталим случајевима GCD кроз DispatchQueue је пожељнији — аутоматски управља пулом нити и потрошњом енергије.
Race condition (трка) настаје када две или више нити истовремено приступају заједничким подацима и бар једна од њих врши упис. Резултат зависи од редоследа извршавања (timing) и непредвидив је. За спречавање race condition-а користе се примитиви синхронизације. У мобилном развоју доступни су блокади (synchronized, NSLock), атомске операције (AtomicInteger, atomic својства у iOS-у) и редови (serial queue).
Избор примитива зависи од сценарија. За једноставне бројаче и заставице довољне су атомске операције (AtomicInteger, atomic property). За критичне секције са више операција — блокади (synchronized, NSLock). За сложене структуре података — serial DispatchQueue или GCD barrier. Блокаде је лакше разумети, али су подложне deadlock-у и livelock-у. Редови су сложенији, али сигурнији.
// Синхронизација у Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — за једноставне бројаче
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — за критичне секције
@Synchronized
fun synchronizedOperation() {
// Само једна нит истовремено
doWork()
}
// 3. Mutex из корутина — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// protected code — безбедно за нити
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// Пример deadlock-а: A блокира B, B блокира 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 демонстрира три приступа синхронизацији. AtomicInteger.incrementAndGet() — атомска операција без блокада (CAS). @Synchronized — уграђени Java монитор, блокира цео објекат. Mutex.withLock — корутински мутекс, суспендује корутину уместо да блокира нит (ефикасније). DeadlockExample приказује класичан deadlock: две нити преузимају блокаде различитим редоследом.
Thread Pool (пул нити) — скуп унапред креираних нити које се поново користе за извршавање задатака. Уместо креирања нове нити за сваки задатак (скупо), пул узима слободну нит из пула. Ако нема слободних нити, задатак се ставља у ред. Пул аутоматски управља величином: нове нити се креирају при вршним оптерећењима, неактивне нити се завршавају. Ово смањује трошак креирања нити десетине пута.
У Android-у Executors.newFixedThreadPool(4) креира пул од 4 нити. Ако истовремено стигне 10 задатака, 4 ће одмах почети, а 6 ће чекати у реду. Executors.newCachedThreadPool() креира нити по потреби (без лимита) и завршава неактивне након 60 секунди. За iOS, GCD аутоматски обезбеђује пулове глобалних редова, чија величина одговара броју CPU језгара и тренутном оптерећењу.
У Kotlin Coroutines пулови нити су скривени унутар диспечера. Dispatchers.Default користи пул величине једнаке броју CPU језгара (најмање 2). Dispatchers.IO — 64 нити (довољно за стотине IO-bound задатака, јер ће већина чекати улаз-излаз без заузимања CPU-ја). Сваки диспечер аутоматски скалира пул према оптерећењу, штедећи енергију батерије током неактивности.
Често постављана питања
Thread — основна јединица извршавања кода у апликацији. Сваки процес може имати много нити које деле меморију, али са сопственим стеком. У мобилном развоју нити се користе за паралелно извршавање задатака без блокирања UI-ја. Android користи Thread, Executors, HandlerThread и Coroutines. iOS користи Thread, GCD (DispatchQueue) и OperationQueue.
Креирање Thread-а захтева издвајање ~1 MB за стек у Android-у и ~512 KB у iOS-у — то је скупа операција. За 1000 задатака директно креирање 1000 нити захтеваће ~1 GB само за стекове плус додатне трошкове за context switch. Уместо Thread-а користите пулове (Executors, GCD) или корутине — оне поново користе нити, смањујући трошкове десетине пута.
Race condition — непредвидиво понашање при истовременом приступу више нити заједничким подацима са уписивањем. Може се избећи на три начина: коришћење атомских типова (AtomicInteger), блокада (synchronized, NSLock) или серијализација приступа кроз ред (DispatchQueue serial, Actor у Kotlin-у). Најбоља пракса — минимизовати заједничко mutable стање и користити immutability.
Thread — нативни системски објекат који заузима ~1 MB стека и везан је за језгро оперативног система. Корутина — лагана јединица извршавања у Kotlin-у која није везана за конкретну нит и може се суспендовати (suspend) без блокирања. Једна нит може да извршава хиљаде корутина. Корутине су ефикасније по меморији и омогућавају писање асинхроног кода без callbacks.
Deadlock се манифестује као потпуно замрзавање апликације без ANR-а. У Android-у користите Thread.getAllStackTraces() за испис стекова свих нити — две нити ће чекати блокаде једна друге. У iOS-у — Thread.callStackSymbols. Алати: Android Studio Profiler (Threads таб), Instruments (iOS, Thread State View). Превенција: преузимајте блокаде фиксним редоследом, користите tryLock са временским лимитом.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође