Lock: co to je, typy zámků a použití v synchronizaci

Autor: IT Sectr Publikováno: 2026-03-19 Doba čtení: 8 min

Lock je synchronizační mechanismus zajišťující exkluzivní přístup ke kritickým sekcím kódu ve vícevláknových aplikacích. Podle Oracle, 2024 poskytuje rozhraní Lock flexibilnější kontrolu synchronizace ve srovnání s tradičními synchronized bloky, včetně pokusů o zámek s timeoutem a podpory více front čekání.

Hlavní body

  • Lock — rozhraní pro explicitní správu zámků v Javě.
  • ReentrantLock — základní implementace s podporou opětovného uzamčení stejným vláknem.
  • ReadWriteLock rozděluje zámky na čtení a zápis pro zvýšení výkonu.
  • Deadlock — hlavní riziko při současném použití více zámků.
  • Na rozdíl od synchronized podporuje Lock time-outy a přerušitelné čekání.

Co je Lock?

Lock je rozhraní z balíčku java.util.concurrent.locks, které poskytuje explicitní operace uzamčení a odemčení pro synchronizaci přístupu k datům. Na rozdíl od synchronized dává Lock vývojáři plnou kontrolu nad mechanismem zámku.

Definice a role v synchronizaci

Rozhraní Lock se objevilo v Javě 5 jako alternativa k vestavěnému mechanismu synchronized. Hlavní metody jsou lock, unlock, tryLock a lockInterruptibly. Zámky umožňují organizovat bezpečný přístup k datům ve vícevláknovém prostředí a zabraňují race condition a poškození dat.

Hlavní výhoda Locku oproti synchronized je flexibilita. Vývojář může zkusit získat zámek s timeoutem, zkontrolovat jeho obsazenost bez blokování nebo organizovat více front čekání s různými prioritami.

Historie vývoje

Před objevením rozhraní Lock v Javě 5 byl jediným způsobem synchronizace synchronized, který trpěl omezeními: nedostatek timeoutů, nemožnost přerušení čekání a jediná fronta. Doug Lea navrhl balíček java.util.concurrent a zahrnul Lock jako základní stavební blok.

Jak funguje zámek?

Zámek řídí přístup prostřednictvím interního stavového příznaku a fronty čekání. Když vlákno zavolá lock(), mechanismus zkontroluje, zda je zámek volný, a buď jej zamkne, nebo umístí vlákno do fronty až do uvolnění.

Atomické uzamčení a odemčení

Základem každého zámku je atomická operace porovnání a nastavení (CAS). Při volání lock() se vlákno pokusí atomicky nastavit příznak obsazenosti. Pokud je příznak již nastaven, vlákno je blokováno. Při unlock() je příznak resetován a jedno z čekajících vláken je probuzeno.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // kritická sekce
        println("Pracuje vlákno ${Thread.currentThread().name}")
    } finally {
        lock.unlock()
    }
}

Fronta čekání a probuzení

ReentrantLock interně používá obousměrnou frontu (CLH lock queue), kde každé čekající vlákno je reprezentováno uzlem. Když je zámek uvolněn, hlavní uzel fronty je probuzen. Režim fair (spravedlivý) garantuje pořadí FIFO, zatímco unfair umožňuje uzamčení novým vláknem před čekajícími pro zvýšení propustnosti.

Hlavní typy zámků

V moderním Java stacku existuje několik implementací zámků, každá optimalizovaná pro konkrétní scénáře. Výběr správného zámku přímo ovlivňuje výkon a spolehlivost vícevláknové aplikace.

ReentrantLock

ReentrantLock — základní a nejčastěji používaná implementace Lock. Podporuje opětovné uzamčení stejným vláknem: pokud vlákno již zámek vlastní, opakované volání lock() jej neblokuje. Tím se předchází deadlocku při rekurzivních voláních.

ReentrantReadWriteLock

ReadWriteLock rozděluje zámky na dva režimy: čtení a zápis. Několik vláken může současně držet zámek pro čtení, ale zápis vyžaduje exkluzivní přístup. To výrazně zvyšuje výkon při častém čtení a vzácném zápisu.

StampedLock

StampedLock — nejnovější implementace, která se objevila v Javě 8. Podporuje tři režimy: zápis, čtení a optimistické čtení. Optimistické čtení neblokuje ostatní vlákna a po přečtení kontroluje platnost dat, což poskytuje 10-20% nárůst výkonu oproti ReadWriteLock.

ZámekVerze JavaRežimyVýkon
ReentrantLockJava 5exkluzivnívysoký
ReadWriteLockJava 5čtení + zápisstřední
StampedLockJava 8čtení + zápis + optimisticvelmi vysoký

ReentrantLock a jeho vlastnosti

ReentrantLock — nejoblíbenější implementace Lock, která poskytuje řadu možností nedostupných v synchronized. Porozumění jeho vlastnostem je nezbytné pro efektivní práci s vícevláknovostí.

Spravedlnost zámku (fairness)

Konstruktor ReentrantLock přijímá parametr fair. Při true zámek garantuje pořadí FIFO přístupu, při false je možné uzamčení novým vláknem před čekajícími. Spravedlivý režim zabraňuje hladovění, ale snižuje propustnost o 10-20% kvůli dodatečným režiím na údržbu fronty.

Timeouty a přerušitelné čekání

Na rozdíl od synchronized podporuje ReentrantLock tryLock s timeoutem. Pokud zámek nelze získat v určeném čase, vlákno pokračuje v provádění místo nekonečného blokování. Metoda lockInterruptibly umožňuje přerušit čekající vlákno pomocí Thread.interrupt().

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Zámek získán")
        } finally {
            lock.unlock()
        }
    } else {
        println("Nepodařilo se získat zámek")
    }
}

Podmínky (Conditions)

ReentrantLock podporuje několik podmínkových proměnných prostřednictvím metody newCondition(). Každá Condition má vlastní frontu čekání, což umožňuje organizovat složité scénáře probuzení. Metody await() a signal() nahradily wait() a notify() z synchronized bloků, ale s podporou více front.

ReadWriteLock a StampedLock

ReadWriteLock a StampedLock řeší problém optimalizace přístupu při převaze operací čtení nad zápisem. Jsou výrazně účinnější než ReentrantLock ve scénářích, kde čtení probíhá častěji než zápis.

ReadWriteLock v praxi

Rozhraní ReadWriteLock obsahuje dvě metody: readLock() a writeLock(). Zámek čtení může být držen několika vlákny současně, zámek zápisu — pouze jedním. Typickým příkladem je thread-safe cache: mnoho vláken čte data a pouze jedno je pravidelně aktualizuje.

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 a optimistické čtení

StampedLock přidává třetí režim — tryOptimisticRead. Tento režim neblokuje ostatní vlákna, pouze si pamatuje razítko (stamp) stavu. Po přečtení vývojář zavolá validate(stamp) a zkontroluje, zda se data během čtení nezměnila. Pokud se data změnila, operaci je třeba opakovat.

Zámky v mobilním vývoji

V mobilních aplikacích se zámky používají ke koordinaci přístupu ke sdíleným datům mezi vlákny. Jejich použití však vyžaduje zvláštní opatrnost kvůli omezeným zdrojům zařízení a potřebě zachovat odezvu rozhraní.

Zámky v Android (Kotlin)

Na Androidu je ReentrantLock užitečný při práci s Room, cache a soubory. Je důležité si pamatovat: nikdy nezamykejte na hlavním vlákně. Pro asynchronní kód jsou preferovány korutiny a Mutex z kotlinx.coroutines, které neblokují vlákno, ale pozastavují korutinu.

Zámky v iOS (Swift)

V iOS se standardní Lock z NSLock používá méně často — vývojáři preferují DispatchQueue s barrier příznaky nebo operační zámky os_unfair_lock. Swift 5.7+ poskytuje moderní synchronizační mechanismy prostřednictvím actors, které automaticky chrání stav.

swift
import Foundation

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

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

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

Doporučení pro zabránění deadlocku

Abyste předešli deadlocku, dodržujte jednotné pořadí uzamykání všech zámků v projektu. Používejte tryLock s timeoutem místo lock() všude tam, kde je možné dlouhodobé blokování. Zvažte použití Lock-Free algoritmů (AtomicReference, ConcurrentHashMap) místo tradičních zámků.

Nejlepší postupy práce s Lock

Použití Lock vyžaduje disciplínu a dodržování několika pravidel, která zabraňují deadlocku a poklesu výkonu. Tyto postupy byly vyvinuty komunitou Java během 20 let používání balíčku java.util.concurrent.

Uvolnění v finally

Nejdůležitější vzor — lock v finally. Bez ohledu na to, zda kritická sekce skončila úspěšně nebo výjimkou, zámek musí být uvolněn. To zaručuje, že ostatní vlákna nebudou navždy zablokována kvůli jedné chybě. V Kotlin je tento vzor elegantně vyřešen pomocí rozšíření withLock.

Minimalizace doby držení

Kritická sekce by měla být maximálně krátká. Nikdy neprovádějte uvnitř zámku vstupně-výstupní operace, síťové požadavky nebo dlouhé výpočty. Pokud potřebujete číst data ze serveru, nejprve je získejte, poté zamkněte pouze pro aktualizaci sdíleného stavu. To snižuje konkurenci a zvyšuje propustnost systému.

Jednotné pořadí uzamykání

Pro zabránění deadlocku při práci s více Lock stanovte globální pořadí uzamykání v celém projektu. Pokud se nejprve zamyká lockA, poté lockB — každé obrácené pořadí musí být zakázáno pravidly code review. Pro automatickou kontrolu používejte statické analyzátory, jako jsou SpotBugs a IntelliJ Inspections.

Často kladené otázky

Jaký je rozdíl mezi Lock a synchronized?

Lock — explicitní rozhraní s možností timeoutu a přerušitelného čekání. synchronized automaticky zamyká a odemyká monitor, ale neumožňuje použití tryLock, lockInterruptibly a více Condition. Lock je flexibilnější, ale vyžaduje ruční uvolnění v finally.

Co je spravedlivý zámek (fair lock)?

Spravedlivý zámek garantuje pořadí FIFO přístupu: vlákno, které čeká nejdéle, dostane zámek jako první. Nespravedlivý zámek může poskytnout přístup novému vláknu obcházejícímu frontu, což zvyšuje propustnost, ale může způsobit hladovění čekajících vláken.

Jak se vyhnout deadlocku při použití Lock?

Dodržujte pevné pořadí uzamykání všech zámků, používejte tryLock s timeoutem místo bezpodmínečného lock a minimalizujte počet současně držených zámků. Použití Lock-Free datových struktur také snižuje riziko deadlocku.

Co je Condition v Lock?

Condition — analog wait/notify pro Lock, umožňující organizovat několik nezávislých front čekání. Každé volání newCondition() vytváří samostatnou frontu, což poskytuje přesnější kontrolu nad probouzením vláken ve srovnání s jedinou frontou synchronized.

Který Lock zvolit pro mobilní aplikaci?

Pro Android s korutinami používejte Mutex z kotlinx.coroutines — ten pozastavuje korutinu, neblokuje vlákno. Pro iOS s Swift 5.7+ jsou preferovány actors, které automaticky synchronizují přístup ke stavu. ReentrantLock ponechte pro starší kód a nízkoúrovňové scénáře.

Shrnutí

  • Lock — rozhraní explicitní správy zámků z java.util.concurrent.locks.
  • ReentrantLock — hlavní implementace s podporou opětovného uzamčení a spravedlnosti.
  • ReadWriteLock rozděluje zámky na čtení a zápis pro read-heavy scénáře.
  • StampedLock přidává optimistické čtení pro maximální výkon.
  • Timeouty a Condition — klíčové výhody Locku oproti synchronized.
  • Deadlocku se předchází pevným pořadím uzamykání a použitím tryLock.
  • V mobilním vývoji jsou doporučeny korutiny (Android) a actors (iOS).

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také