Lock: wat is het, soorten vergrendelingen en gebruik in synchronisatie

Auteur: IT Sectr Gepubliceerd: 2026-03-19 Leestijd: 8 min

Lock is een synchronisatiemechanisme dat exclusieve toegang biedt tot kritieke secties van code in multithreading-applicaties. Volgens Oracle, 2024 biedt de Lock-interface een flexibelere synchronisatiecontrole vergeleken met traditionele synchronized-blokken, inclusief pogingen tot vergrendeling met timeout en ondersteuning voor meerdere wachtrijen.

Belangrijkste punten

  • Lock — interface voor expliciet beheer van vergrendelingen in Java.
  • ReentrantLock — basisimplementatie met ondersteuning voor hervergrendeling door dezelfde thread.
  • ReadWriteLock splitst vergrendelingen in lezen en schrijven voor betere prestaties.
  • Deadlock — het grootste risico bij gelijktijdig gebruik van meerdere vergrendelingen.
  • In tegenstelling tot synchronized ondersteunt Lock timeouts en onderbreekbaar wachten.

Wat is Lock?

Lock is een interface uit het pakket java.util.concurrent.locks die expliciete vergrendelings- en ontgrendelingsoperaties biedt voor synchronisatie van gegevens toegang. In tegenstelling tot synchronized geeft Lock de ontwikkelaar volledige controle over het vergrendelingsmechanisme.

Definitie en rol in synchronisatie

De Lock-interface verscheen in Java 5 als alternatief voor het ingebouwde synchronized-mechanisme. De belangrijkste methoden zijn lock, unlock, tryLock en lockInterruptibly. Vergrendelingen maken veilige toegang tot gegevens in een multithreading-omgeving mogelijk, waardoor racecondities en gegevensbeschadiging worden voorkomen.

Het belangrijkste voordeel van Lock ten opzichte van synchronized is flexibiliteit. De ontwikkelaar kan proberen een vergrendeling met timeout te verkrijgen, de bezetting controleren zonder te blokkeren, of meerdere wachtrijen met verschillende prioriteiten organiseren.

Geschiedenis van ontwikkeling

Vóór de komst van de Lock-interface in Java 5 was de enige manier van synchronisatie synchronized, dat leed onder beperkingen: gebrek aan timeouts, onmogelijkheid om wachten te onderbreken en een enkele wachtrij. Doug Lea ontwierp het pakket java.util.concurrent en nam Lock op als fundamenteel bouwblok.

Hoe werkt een vergrendeling?

Vergrendeling beheert toegang via een interne statusvlag en een wachtrij. Wanneer een thread lock() aanroept, controleert het mechanisme of de vergrendeling vrij is en vergrendelt deze of plaatst de thread in de wachtrij tot vrijgave.

Atomair vergrendelen en ontgrendelen

Aan de basis van elke vergrendeling ligt een atomaire vergelijk-en-vervang (CAS)-operatie. Bij lock() probeert de thread atomair de bezetvlag te zetten. Als de vlag al is gezet, wordt de thread geblokkeerd. Bij unlock() wordt de vlag gereset en wordt een van de wachtende threads gewekt.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // kritieke sectie
        println("Thread ${Thread.currentThread().name} werkt")
    } finally {
        lock.unlock()
    }
}

Wachtrij en wekken

ReentrantLock gebruikt intern een tweerichtingswachtrij (CLH lock queue), waarbij elke wachtende thread wordt vertegenwoordigd door een knoop. Wanneer de vergrendeling wordt vrijgegeven, wordt de hoofdknoop van de wachtrij gewekt. De fair-modus garandeert FIFO-volgorde, terwijl unfair het mogelijk maakt dat een nieuwe thread vóór wachtenden vergrendelt voor hogere doorvoer.

Belangrijkste soorten vergrendelingen

In de moderne Java-stack bestaan meerdere implementaties van vergrendelingen, elk geoptimaliseerd voor specifieke scenario's. De juiste keuze van vergrendeling beïnvloedt direct de prestaties en betrouwbaarheid van een multithreading-applicatie.

ReentrantLock

ReentrantLock — de basis en meest gebruikte implementatie van Lock. Het ondersteunt hervergrendeling door dezelfde thread: als een thread de vergrendeling al bezit, blokkeert een herhaalde lock()-aanroep niet. Dit voorkomt deadlock bij recursieve aanroepen.

ReentrantReadWriteLock

ReadWriteLock splitst vergrendelingen in twee modi: lezen en schrijven. Meerdere threads kunnen gelijktijdig de leesvergrendeling vasthouden, maar schrijven vereist exclusieve toegang. Dit verhoogt de prestaties aanzienlijk bij veel lezen en weinig schrijven.

StampedLock

StampedLock — de nieuwste implementatie, verschenen in Java 8. Het ondersteunt drie modi: schrijven, lezen en optimistisch lezen. Optimistisch lezen blokkeert andere threads niet en controleert de geldigheid van gegevens na het lezen, wat een prestatieverbetering van 10-20% oplevert ten opzichte van ReadWriteLock.

VergrendelingJava-versieModiPrestaties
ReentrantLockJava 5exclusiefhoog
ReadWriteLockJava 5lezen + schrijvengemiddeld
StampedLockJava 8lezen + schrijven + optimisticzeer hoog

ReentrantLock en zijn kenmerken

ReentrantLock — de populairste implementatie van Lock, die een reeks mogelijkheden biedt die niet beschikbaar zijn in synchronized. Inzicht in de kenmerken is essentieel voor effectief werken met multithreading.

Eerlijkheid van de vergrendeling (fairness)

De constructor van ReentrantLock accepteert de parameter fair. Bij true garandeert de vergrendeling FIFO-volgorde van toegang, bij false is vergrendeling door een nieuwe thread vóór wachtenden mogelijk. Eerlijke modus voorkomt uithongering, maar verlaagt de doorvoer met 10-20% vanwege extra overhead voor het onderhouden van de wachtrij.

Timeouts en onderbreekbaar wachten

In tegenstelling tot synchronized ondersteunt ReentrantLock tryLock met timeout. Als de vergrendeling niet binnen de opgegeven tijd kan worden verkregen, gaat de thread verder met uitvoeren in plaats van oneindig te blokkeren. De methode lockInterruptibly maakt het mogelijk een wachtende thread te onderbreken via Thread.interrupt().

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Vergrendeling verkregen")
        } finally {
            lock.unlock()
        }
    } else {
        println("Vergrendeling kon niet worden verkregen")
    }
}

Condities (Conditions)

ReentrantLock ondersteunt meerdere conditievariabelen via de methode newCondition(). Elke Condition heeft een eigen wachtrij, wat het mogelijk maakt complexe wekscenario's te organiseren. De methoden await() en signal() hebben wait() en notify() uit synchronized-blokken vervangen, maar met ondersteuning voor meerdere wachtrijen.

ReadWriteLock en StampedLock

ReadWriteLock en StampedLock lossen het probleem van toegangsoptimalisatie op wanneer leesoperaties overheersen over schrijven. Ze zijn aanzienlijk efficiënter dan ReentrantLock in scenario's waar lezen vaker voorkomt dan schrijven.

ReadWriteLock in de praktijk

De ReadWriteLock-interface bevat twee methoden: readLock() en writeLock(). De leesvergrendeling kan gelijktijdig door meerdere threads worden vastgehouden, de schrijfvergrendeling slechts door één. Een typisch voorbeeld — een thread-safe cache: meerdere threads lezen gegevens en slechts één werkt ze periodiek bij.

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 en optimistisch lezen

StampedLock voegt een derde modus toe — tryOptimisticRead. Deze modus blokkeert andere threads niet, maar onthoudt alleen een stempel (stamp) van de status. Na het lezen roept de ontwikkelaar validate(stamp) aan om te controleren of de gegevens zijn gewijzigd tijdens het lezen. Als de gegevens zijn gewijzigd, moet de operatie worden herhaald.

Vergrendelingen in mobiele ontwikkeling

In mobiele applicaties worden vergrendelingen gebruikt voor coördinatie van toegang tot gedeelde gegevens tussen threads. Het gebruik ervan vereist echter speciale voorzichtigheid vanwege de beperkte apparaatbronnen en de noodzaak om de responsiviteit van de interface te behouden.

Vergrendelingen in Android (Kotlin)

Op Android is ReentrantLock nuttig bij het werken met Room, caches en bestanden. Het is belangrijk te onthouden: vergrendel nooit op de hoofdthread. Voor asynchrone code verdienen coroutines en Mutex uit kotlinx.coroutines de voorkeur, die de thread niet blokkeren maar de coroutine onderbreken.

Vergrendelingen in iOS (Swift)

In iOS wordt standaard Lock van NSLock minder gebruikt — ontwikkelaars geven de voorkeur aan DispatchQueue met barrier-vlaggen of operationele vergrendelingen os_unfair_lock. Swift 5.7+ biedt moderne synchronisatiemechanismen via actors, die automatisch de status beschermen.

swift
import Foundation

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

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

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

Aanbevelingen voor het vermijden van deadlock

Om deadlock te voorkomen, volg één enkele vergrendelingsvolgorde voor alle vergrendelingen in het project. Gebruik tryLock met timeout in plaats van lock() overal waar langdurige blokkering mogelijk is. Overweeg Lock-Free algoritmen (AtomicReference, ConcurrentHashMap) in plaats van traditionele vergrendelingen.

Beste praktijken voor werken met Lock

Het gebruik van Lock vereist discipline en naleving van verschillende regels die deadlock en prestatieverlies voorkomen. Deze praktijken zijn ontwikkeld door de Java-gemeenschap in 20 jaar gebruik van het pakket java.util.concurrent.

Vrijgeven in finally

Het belangrijkste patroon — lock in finally. Ongeacht of de kritieke sectie succesvol of met een uitzondering is beëindigd, moet de vergrendeling worden vrijgegeven. Dit garandeert dat andere threads niet voor altijd worden geblokkeerd door één fout. In Kotlin wordt dit patroon elegant opgelost via de extensie withLock.

Minimaliseren van de vasthoudtijd

De kritieke sectie moet maximaal kort zijn. Voer nooit invoer-uitvoer, netwerkverzoeken of lange berekeningen uit binnen een vergrendeling. Als u gegevens van een server moet lezen, haal ze dan eerst op en vergrendel alleen voor het bijwerken van de gedeelde status. Dit vermindert concurrentie en verhoogt de systeemdoorvoer.

Één enkele vergrendelingsvolgorde

Om deadlock bij het werken met meerdere Lock te voorkomen, stel globale vergrendelingsvolgorde vast in het hele project. Als eerst lockA en daarna lockB wordt verkregen — moet elke omgekeerde volgorde worden verboden door code review-regels. Gebruik statische analysatoren zoals SpotBugs en IntelliJ Inspections voor automatische controle.

Veelgestelde vragen

Wat is het verschil tussen Lock en synchronized?

Lock — expliciete interface met mogelijkheid tot timeout en onderbreekbaar wachten. synchronized vergrendelt en ontgrendelt automatisch de monitor, maar staat geen tryLock, lockInterruptibly en meerdere Condition toe. Lock is flexibeler, maar vereist handmatige vrijgave in finally.

Wat is een eerlijke vergrendeling (fair lock)?

Een eerlijke vergrendeling garandeert FIFO-volgorde van toegang: de thread die het langst wacht, krijgt als eerste de vergrendeling. Oneerlijke vergrendeling kan toegang geven aan een nieuwe thread die de wachtrij omzeilt, wat de doorvoer verhoogt maar kan leiden tot uithongering van wachtende threads.

Hoe deadlock voorkomen bij gebruik van Lock?

Volg vaste volgorde van vergrendeling van alle locks, gebruik tryLock met timeout in plaats van onvoorwaardelijke lock en minimaliseer het aantal gelijktijdig vastgehouden vergrendelingen. Toepassing van Lock-Free gegevensstructuren vermindert ook het risico op deadlock.

Wat is Condition in Lock?

Condition — de tegenhanger van wait/notify voor Lock, waarmee meerdere onafhankelijke wachtrijen kunnen worden georganiseerd. Elke aanroep van newCondition() creëert een aparte wachtrij, wat meer precieze controle geeft over het wekken van threads vergeleken met de enkele wachtrij van synchronized.

Welke Lock kiezen voor een mobiele app?

Voor Android met coroutines gebruik Mutex van kotlinx.coroutines — het onderbreekt de coroutine, niet de thread. Voor iOS met Swift 5.7+ hebben actors de voorkeur, die automatisch toegang tot de status synchroniseren. Houd ReentrantLock voor legacy-code en low-level scenario's.

Samenvatting

  • Lock — interface voor expliciet beheer van vergrendelingen uit java.util.concurrent.locks.
  • ReentrantLock — basisimplementatie met ondersteuning voor hervergrendeling en eerlijkheid.
  • ReadWriteLock splitst vergrendelingen in lezen en schrijven voor read-heavy scenario's.
  • StampedLock voegt optimistisch lezen toe voor maximale prestaties.
  • Timeouts en Condition — belangrijkste voordelen van Lock ten opzichte van synchronized.
  • Deadlock wordt voorkomen door vaste vergrendelingsvolgorde en gebruik van tryLock.
  • In mobiele ontwikkeling worden coroutines (Android) en actors (iOS) aanbevolen.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook