Lock: шта је то, типови блокирања и употреба у синхронизацији

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

Lock је механизам синхронизације који обезбеђује ексклузивни приступ критичним секцијама кода у вишегрупим апликацијама. Према Oracle, 2024, Lock интерфејс пружа флексибилнију контролу синхронизације у поређењу са традиционалним synchronized блоковима, укључујући покушаје преузимања са тајмаутом и подршку за више редова чекања.

Главне тачке

  • Lock — интерфејс за експлицитно управљање блокирањима у Java.
  • ReentrantLock — основна имплементација са подршком за поновно преузимање од исте нити.
  • ReadWriteLock раздваја блокирања на читање и писање ради повећања перформанси.
  • Deadlock — главни ризик при истовременом коришћењу више блокирања.
  • За разлику од synchronized, Lock подржава тајмауте и прекидно чекање.

Шта је Lock?

Lock је интерфејс из пакета java.util.concurrent.locks који пружа експлицитне операције преузимања и ослобађања ради синхронизације приступа подацима. За разлику од synchronized, Lock даје програмеру потпуну контролу над механизмом блокирања.

Дефиниција и улога у синхронизацији

Lock интерфејс се појавио у Java 5 као алтернатива уграђеном synchronized механизму. Главне методе су lock, unlock, tryLock и lockInterruptibly. Блокирања омогућавају организовање безбедног приступа подацима у вишегрупом окружењу, спречавајући стања трке и оштећење података.

Главна предност Lock у односу на synchronized је флексибилност. Програмер може покушати да преузме блокирање са тајмаутом, провери његову заузетост без блокирања или организовати више редова чекања са различитим приоритетима.

Историја развоја

Пре појаве Lock интерфејса у Java 5, једини начин синхронизације био је synchronized, који је патио од ограничења: недостатак тајмаута, немогућност прекида чекања и један ред. Даг Ли је дизајнирао пакет java.util.concurrent, укључивши Lock као основни градивни блок.

Како ради блокирање?

Блокирање управља приступом кроз интерну заставицу стања и ред чекања. Када нит позове lock(), механизам проверава да ли је блокирање слободно и или га преузима или смешта нит у ред до ослобађања.

Атомско преузимање и ослобађање

У основи сваког блокирања лежи атомска операција поређења и постављања (CAS). При позиву lock(), нит покушава атомски да постави заставицу заузетости. Ако је заставица већ постављена, нит се блокира. При unlock(), заставица се ресетује и буди се једна од нити у чекању.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // критична секција
        println("Ради нит ${Thread.currentThread().name}")
    } finally {
        lock.unlock()
    }
}

Ред чекања и буђење

ReentrantLock интерно користи двострани ред (CLH lock queue), где је свака нит у чекању представљена чвором. Када се блокирање ослободи, главни чвор реда се буди. Fair (поштени) режим гарантује FIFO редослед, а unfair дозвољава преузимање од нове нити пре оних у чекању ради повећања пропусности.

Основне врсте блокирања

У модерном Java стек-у постоји неколико имплементација блокирања, свака оптимизована за конкретне сценарије. Избор правог блокирања директно утиче на перформансе и поузданост вишегрупе апликације.

ReentrantLock

ReentrantLock — основна и најчешће коришћена имплементација Lock-а. Подржава поновно преузимање од исте нити: ако нит већ поседује блокирање, поновни позив lock() је не блокира. Ово спречава deadlock при рекурзивним позивима.

ReentrantReadWriteLock

ReadWriteLock раздваја блокирања на два режима: читање и писање. Више нити може истовремено држати блокирање читања, али писање захтева ексклузивни приступ. Ово значајно повећава перформансе при честом читању и ретком писању.

StampedLock

StampedLock — најновија имплементација, која се појавила у Java 8. Подржава три режима: писање, читање и оптимистичко читање. Оптимистичко читање не блокира друге нити и проверава валидност података након читања, што даје повећање перформанси од 10-20% у поређењу са ReadWriteLock.

БлокирањеJava верзијаРежимиПерформансе
ReentrantLockJava 5ексклузивнивисоке
ReadWriteLockJava 5читање + писањесредње
StampedLockJava 8читање + писање + optimisticврло високе

ReentrantLock и његове карактеристике

ReentrantLock — најпопуларнија имплементација Lock-а, која пружа низ могућности недоступних у synchronized. Разумевање његових карактеристика је неопходно за ефикасан рад са вишегрупошћу.

Поштеност блокирања (fairness)

Конструктор ReentrantLock прихвата параметар fair. При true блокирање гарантује FIFO редослед приступа, при false је могуће преузимање од нове нити пре оних у чекању. Поштени режим спречава гладовање, али смањује пропусност за 10-20% због додатних трошкова одржавања реда.

Тајмаути и прекидно чекање

За разлику од synchronized, ReentrantLock подржава tryLock са тајмаутом. Ако блокирање није могло бити преузето у одређеном времену, нит наставља извршење уместо да се блокира бесконачно. Метода lockInterruptibly омогућава прекид нити у чекању путем Thread.interrupt().

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Блокирање преузето")
        } finally {
            lock.unlock()
        }
    } else {
        println("Није успело преузимање блокирања")
    }
}

Услови (Conditions)

ReentrantLock подржава више условних променљивих кроз методу newCondition(). Сваки Condition има сопствени ред чекања, што омогућава организовање сложених сценарија буђења. Методе await() и signal() замениле су wait() и notify() из synchronized блокова, али са подршком за више редова.

ReadWriteLock и StampedLock

ReadWriteLock и StampedLock решавају проблем оптимизације приступа када операције читања преовлађују над писањем. Они су значајно ефикаснији од ReentrantLock-а у сценаријима где се читање дешава чешће од писања.

ReadWriteLock у пракси

ReadWriteLock интерфејс садржи две методе: readLock() и writeLock(). Блокирање читања може држати више нити истовремено, блокирање писања — само једна. Типичан пример — безбедни кеш за нити: више нити чита податке, а само једна их повремено ажурира.

kotlin
class SafeCache<K, V> {
    private val map = mutableMapOf<K, V>()
    private val rwLock = ReentrantReadWriteLock()

    fun get(key: K): V? {
        rwLock.readLock().lock()
        return try { map[key] } finally { rwLock.readLock().unlock() }
    }

    fun put(key: K, value: V) {
        rwLock.writeLock().lock()
        return try { map[key] = value } finally { rwLock.writeLock().unlock() }
    }
}

StampedLock и оптимистичко читање

StampedLock додаје трећи режим — tryOptimisticRead. Овај режим не блокира друге нити, већ само памти печат (stamp) стања. Након читања, програмер позива validate(stamp) да провери да ли су се подаци променили током читања. Ако су се подаци променили, операција се мора поновити.

Блокирања у мобилном развоју

У мобилним апликацијама, блокирања се користе за координацију приступа заједничким подацима између нити. Међутим, њихова примена захтева посебну опрезност због ограничених ресурса уређаја и потребе за одржавањем одзивности интерфејса.

Блокирања у Android (Kotlin)

На Android-у, ReentrantLock је користан при раду са Room, кешевима и датотекама. Важно је запамтити: никада не преузимајте блокирање на главној нити. За асинхрони код, пожељније су корутине и Mutex из kotlinx.coroutines, који не блокирају нит већ суспендују корутину.

Блокирања у iOS (Swift)

У iOS-у, стандардни Lock из NSLock се ређе користи — програмери преферирају DispatchQueue са barrier заставицама или операциона блокирања os_unfair_lock. Swift 5.7+ пружа модерне механизме синхронизације кроз actors, који аутоматски штите стање.

swift
import Foundation

actor DataStore {
    private var items: [String] = []

    func add(_ item: String) {
        items.append(item)
    }

    func getAll() -> [String] {
        items
    }
}

Препоруке за избегавање deadlock-а

Да бисте избегли deadlock, поштујте јединствени редослед преузимања свих блокирања у пројекту. Користите tryLock са тајмаутом уместо lock() свуда где је могуће дуготрајно блокирање. Размотрите примену Lock-Free алгоритама (AtomicReference, ConcurrentHashMap) уместо традиционалних блокирања.

Најбоље праксе за рад са Lock

Примена Lock захтева дисциплину и поштовање неколико правила која спречавају deadlock и пад перформанси. Ове праксе су развијене од стране Java заједнице током 20 година коришћења пакета java.util.concurrent.

Ослобађање у finally

Најважнији образац — lock у finally. Без обзира да ли се критична секција завршила успешно или изузетком, блокирање мора бити ослобођено. Ово гарантује да друге нити неће бити заувек блокиране због једне грешке. У Kotlin-у, овај образац је елегантно решен кроз екстензију withLock.

Минимизација времена држања

Критична секција треба да буде максимално кратка. Никада не извршавајте унутар блокирања улазно-излазне операције, мрежне захтеве или дуготрајна израчунавања. Ако треба да прочитате податке са сервера, прво их добијте, а затим преузмите блокирање само за ажурирање заједничког стања. Ово смањује конкуренцију и повећава пропусност система.

Јединствени редослед преузимања

За спречавање deadlock-а при раду са више Lock-ова, утврдите глобални редослед преузимања у целом пројекту. Ако се прво преузима lockA, затим lockB — сваки обрнути редослед мора бити забрањен правилима code review-а. За аутоматску проверу користите статичке анализаторе попут SpotBugs и IntelliJ Inspections.

Често постављана питања

Која је разлика између Lock и synchronized?

Lock — експлицитни интерфејс са могућношћу тајмаута и прекидног чекања. synchronized аутоматски преузима и ослобађа монитор, али не дозвољава коришћење tryLock, lockInterruptibly и више Condition. Lock је флексибилнији, али захтева ручно ослобађање у finally.

Шта је поштено блокирање (fair lock)?

Поштено блокирање гарантује FIFO редослед приступа: нит која најдуже чека прва добија блокирање. Непоштено блокирање може дати приступ новој нити заобилазећи ред, што повећава пропусност, али може изазвати гладовање нити у чекању.

Како избећи deadlock при коришћењу Lock?

Поштујте фиксни редослед преузимања свих блокирања, користите tryLock са тајмаутом уместо безусловног lock и минимизирајте број истовремено држаних блокирања. Примена Lock-Free структура података такође смањује ризик од deadlock-а.

Шта је Condition у Lock?

Condition — аналог wait/notify за Lock, који омогућава организовање више независних редова чекања. Сваки позив newCondition() ствара посебан ред, што даје прецизнију контролу над буђењем нити у поређењу са једним редом synchronized.

Који Lock изабрати за мобилну апликацију?

За Android са корутинама користите Mutex из kotlinx.coroutines — он суспендује корутину, а не блокира нит. За iOS са Swift 5.7+, пожељни су actors, који аутоматски синхронизују приступ стању. ReentrantLock оставите за стари код и нисконивоске сценарије.

Резиме

  • Lock — интерфејс експлицитног управљања блокирањима из java.util.concurrent.locks.
  • ReentrantLock — основна имплементација са подршком за поновно преузимање и поштеност.
  • ReadWriteLock раздваја блокирања на читање и писање за read-heavy сценарије.
  • StampedLock додаје оптимистичко читање за максималне перформансе.
  • Тајмаути и Condition — кључне предности Lock у односу на synchronized.
  • Deadlock се спречава фиксним редоследом преузимања и коришћењем tryLock.
  • У мобилном развоју препоручују се корутине (Android) и actors (iOS).

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

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

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

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