Thread у мобилном развоју — шта је, врсте и управљање нитима

Аутор: IT Sectr Објављено: 2026-03-16 Време читања: 11 мин

Thread — основна јединица процесорског времена, која има сопствени стек и извршава се независно од других нити. У мобилном развоју, нити се користе за паралелно извршавање задатака како би интерфејс остао одзиван током дугих операција. Android подржава java.lang.Thread, Executors и Kotlin Coroutines, iOS — Thread (Objective-C), GCD и OperationQueue. Према Android Thread Documentation, креирање нативне нити захтева да оперативни систем издвоји ~1 MB за стек.

Кључно

  • Thread — минимална јединица планирања CPU-ја: свака нит је независна и има свој стек
  • Креирање нити захтева ~1 MB за стек у Android-у и 512 KB у iOS-у, па су пулови ефикаснији од директног креирања
  • Android: Thread, Executors, HandlerThread, Coroutines — четири нивоа апстракције нити
  • iOS: Thread (нискоризински), GCD (DispatchQueue), OperationQueue (високоризински)
  • Thread safety — заједнички приступ mutable подацима захтева синхронизацију: locks, atomic, serial queues

Шта је Thread

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 и његова цена

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.

Thread у Android-у: од Thread-а до Coroutines

Android је еволуирао од нискоризинског java.lang.Thread-а до савремених корутина. Сваки ниво апстракције даје више могућности уз мање додатне трошкове. Thread је основна класа, али њено директно креирање није препоручљиво: нова нит није под управом пула, тешко је пратити и отказати. AsyncTask (deprecated од API 30) је био корак напред, али је патио од цурења меморије и незгодне обраде конфигурација.

HandlerThread — посебна поткласа Thread-а са Looper-ом, која може да обрађује ред порука. Користи се за секвенцијално извршавање задатака у позадинској нити, на пример, уписивање података у Room или у датотеке. HandlerThread се креира позивом start(), након чега се кроз Handler(handlerThread.looper) могу слати поруке и Runnable. Позив handlerThread.quit() зауставља Looper и завршава нит.

kotlin
// 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: секвенцијални позадински задаци

HandlerThread — специјализована поткласа Thread-а са уграђеним Looper-ом и редом порука. Креира се позивом start(), након чега се кроз Handler(handlerThread.looper) могу слати Runnable и поруке. HandlerThread извршава задатке строго секвенцијално — следећи задатак не почиње док се претходни не заврши. Ово је згодно за уписивање података у Room или датотеке, где је редослед операција критичан. Позив quitSafely() зауставља Looper након завршетка текућег задатка.

Thread у iOS-у: Thread, GCD и OperationQueue

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.

swift
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 блокова, јер не блокира читаоце док не постоји упис.

iOS Thread vs GCD: када директно користити Thread

Директна употреба Thread-а у iOS-у је оправдана у три случаја: за thread-local storage (Thread.current.threadDictionary) — чување података везаних за нит; за креирање посебног RunLoop-а у позадинској нити са performSelector:onThread:; за интеграцију са C/C++ библиотекама које очекују pthread_t. У свим осталим случајевима GCD кроз DispatchQueue је пожељнији — аутоматски управља пулом нити и потрошњом енергије.

Синхронизација нити: locks, atomic, serial queues

Race condition (трка) настаје када две или више нити истовремено приступају заједничким подацима и бар једна од њих врши упис. Резултат зависи од редоследа извршавања (timing) и непредвидив је. За спречавање race condition-а користе се примитиви синхронизације. У мобилном развоју доступни су блокади (synchronized, NSLock), атомске операције (AtomicInteger, atomic својства у iOS-у) и редови (serial queue).

Избор примитива зависи од сценарија. За једноставне бројаче и заставице довољне су атомске операције (AtomicInteger, atomic property). За критичне секције са више операција — блокади (synchronized, NSLock). За сложене структуре података — serial DispatchQueue или GCD barrier. Блокаде је лакше разумети, али су подложне deadlock-у и livelock-у. Редови су сложенији, али сигурнији.

kotlin
// Синхронизација у 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 Pools: зашто је Executors бољи од Thread-а

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 у мобилном развоју?

Thread — основна јединица извршавања кода у апликацији. Сваки процес може имати много нити које деле меморију, али са сопственим стеком. У мобилном развоју нити се користе за паралелно извршавање задатака без блокирања UI-ја. Android користи Thread, Executors, HandlerThread и Coroutines. iOS користи Thread, GCD (DispatchQueue) и OperationQueue.

Зашто се не препоручује директно креирање Thread-а?

Креирање Thread-а захтева издвајање ~1 MB за стек у Android-у и ~512 KB у iOS-у — то је скупа операција. За 1000 задатака директно креирање 1000 нити захтеваће ~1 GB само за стекове плус додатне трошкове за context switch. Уместо Thread-а користите пулове (Executors, GCD) или корутине — оне поново користе нити, смањујући трошкове десетине пута.

Шта је race condition и како је избећи?

Race condition — непредвидиво понашање при истовременом приступу више нити заједничким подацима са уписивањем. Може се избећи на три начина: коришћење атомских типова (AtomicInteger), блокада (synchronized, NSLock) или серијализација приступа кроз ред (DispatchQueue serial, Actor у Kotlin-у). Најбоља пракса — минимизовати заједничко mutable стање и користити immutability.

По чему се Thread разликује од корутине?

Thread — нативни системски објекат који заузима ~1 MB стека и везан је за језгро оперативног система. Корутина — лагана јединица извршавања у Kotlin-у која није везана за конкретну нит и може се суспендовати (suspend) без блокирања. Једна нит може да извршава хиљаде корутина. Корутине су ефикасније по меморији и омогућавају писање асинхроног кода без callbacks.

Како ухватити deadlock у мобилној апликацији?

Deadlock се манифестује као потпуно замрзавање апликације без ANR-а. У Android-у користите Thread.getAllStackTraces() за испис стекова свих нити — две нити ће чекати блокаде једна друге. У iOS-у — Thread.callStackSymbols. Алати: Android Studio Profiler (Threads таб), Instruments (iOS, Thread State View). Превенција: преузимајте блокаде фиксним редоследом, користите tryLock са временским лимитом.

Закључци

  • Thread — минимална јединица CPU-ја: независно извршавање са сопственим стеком, меморија хипа је заједничка
  • Пет стања нити: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android је еволуирао од Thread-а → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS пружа Thread, GCD (DispatchQueue) и OperationQueue — од ниског ка високом нивоу
  • Race condition се решава блокадама (synchronized, NSLock), атомским типовима и serial редовима
  • Deadlock настаје при укрштеном преузимању блокада — спречава се фиксним редоследом
  • Thread Pool је ефикаснији од креирања нових Thread-ова: поново користи нити, смањује трошак за context switch

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође