Lock: vad är det, typer av låsning och användning i synkronisering

Författare: IT Sectr Publicerad: 2026-03-19 Lästid: 8 min

Lock är en synkroniseringsmekanism som ger exklusiv åtkomst till kritiska sektioner av kod i flertrådade applikationer. Enligt Oracle, 2024 ger Lock-gränssnittet mer flexibel synkroniseringskontroll jämfört med traditionella synchronized-block, inklusive försök till låsning med timeout och stöd för flera vänteköer.

Huvudpunkter

  • Lock — gränssnitt för explicit hantering av låsningar i Java.
  • ReentrantLock — grundimplementering med stöd för återlåsning av samma tråd.
  • ReadWriteLock delar upp låsningar i läsning och skrivning för ökad prestanda.
  • Deadlock — den största risken vid samtidig användning av flera låsningar.
  • Till skillnad från synchronized stöder Lock timeout och avbrytbar väntan.

Vad är Lock?

Lock är ett gränssnitt från paketet java.util.concurrent.locks som tillhandahåller explicita låsnings- och upplåsningsoperationer för synkronisering av dataåtkomst. Till skillnad från synchronized ger Lock utvecklaren full kontroll över låsningsmekanismen.

Definition och roll i synkronisering

Lock-gränssnittet dök upp i Java 5 som ett alternativ till den inbyggda synchronized-mekanismen. Huvudmetoderna är lock, unlock, tryLock och lockInterruptibly. Låsningar möjliggör organisering av säker dataåtkomst i en flertrådad miljö, vilket förhindrar race conditions och dataskada.

Den främsta fördelen med Lock jämfört med synchronized är flexibilitet. Utvecklaren kan försöka låsa med timeout, kontrollera upptagenhet utan blockering, eller organisera flera vänteköer med olika prioriteter.

Utvecklingshistoria

Innan Lock-gränssnittet kom i Java 5 var det enda sättet för synkronisering synchronized, som led av begränsningar: brist på timeout, omöjlighet att avbryta väntan och en enda kö. Doug Lea designade paketet java.util.concurrent och inkluderade Lock som en fundamental byggsten.

Hur fungerar låsning?

Låsning hanterar åtkomst via en intern statusflagga och väntekö. När en tråd anropar lock() kontrollerar mekanismen om låsningen är ledig och låser den antingen eller placerar tråden i kön tills frigivning.

Atomisk låsning och upplåsning

Grunden för varje låsning är en atomisk jämför-och-sätt (CAS)-operation. Vid anrop av lock() försöker tråden atomiskt sätta upptagenhetsflaggan. Om flaggan redan är satt blockeras tråden. Vid unlock() återställs flaggan och en av de väntande trådarna väcks.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // kritisk sektion
        println("Tråden ${Thread.currentThread().name} arbetar")
    } finally {
        lock.unlock()
    }
}

Väntekö och väckning

ReentrantLock använder internt en dubbelriktad kö (CLH lock queue), där varje väntande tråd representeras av en nod. När låsningen frigörs väcks huvudnoden i kön. Fair-läge (rättvist) garanterar FIFO-ordning, medan unfair tillåter en ny tråd att låsa före väntande för ökad genomströmning.

Huvudtyper av låsningar

I den moderna Java-stacken finns flera implementeringar av låsningar, var och en optimerad för specifika scenarier. Valet av rätt låsning påverkar direkt prestanda och tillförlitlighet hos en flertrådad applikation.

ReentrantLock

ReentrantLock — den grundläggande och mest använda implementeringen av Lock. Den stöder återlåsning av samma tråd: om en tråd redan har låsningen blockerar inte ett upprepat lock()-anrop. Detta förhindrar deadlock vid rekursiva anrop.

ReentrantReadWriteLock

ReadWriteLock delar upp låsningar i två lägen: läsning och skrivning. Flera trådar kan samtidigt hålla läsningslåsningen, men skrivning kräver exklusiv åtkomst. Detta ökar prestandan avsevärt vid frekvent läsning och sällsynt skrivning.

StampedLock

StampedLock — den senaste implementeringen, som dök upp i Java 8. Den stöder tre lägen: skrivning, läsning och optimistisk läsning. Optimistisk läsning blockerar inte andra trådar och validerar data efter läsning, vilket ger 10-20% prestandaökning jämfört med ReadWriteLock.

LåsningJava-versionLägenPrestanda
ReentrantLockJava 5exklusivhög
ReadWriteLockJava 5läsning + skrivningmedel
StampedLockJava 8läsning + skrivning + optimisticmycket hög

ReentrantLock och dess egenskaper

ReentrantLock — den populäraste implementeringen av Lock, som erbjuder en rad möjligheter som inte finns i synchronized. Förståelse av dess egenskaper är nödvändig för effektivt arbete med flertrådighet.

Rättvisa hos låsning (fairness)

Konstruktorn för ReentrantLock accepterar parametern fair. Vid true garanterar låsningen FIFO-ordning för åtkomst, vid false är låsning av en ny tråd före väntande möjlig. Rättvist läge förhindrar svält men minskar genomströmningen med 10-20% på grund av extra overhead för köhantering.

Timeout och avbrytbar väntan

Till skillnad från synchronized stöder ReentrantLock tryLock med timeout. Om låsningen inte kan erhållas inom angiven tid fortsätter tråden exekvering istället för att blockeras oändligt. Metoden lockInterruptibly gör det möjligt att avbryta en väntande tråd via Thread.interrupt().

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Låsning erhållen")
        } finally {
            lock.unlock()
        }
    } else {
        println("Det gick inte att erhålla låsning")
    }
}

Villkor (Conditions)

ReentrantLock stöder flera villkorsvariabler via metoden newCondition(). Varje Condition har sin egen väntekö, vilket möjliggör organisering av komplexa väckningsscenarier. Metoderna await() och signal() har ersatt wait() och notify() från synchronized-block, men med stöd för flera köer.

ReadWriteLock och StampedLock

ReadWriteLock och StampedLock löser problemet med åtkomstoptimering när läsoperationer dominerar över skrivning. De är betydligt effektivare än ReentrantLock i scenarier där läsning sker oftare än skrivning.

ReadWriteLock i praktiken

ReadWriteLock-gränssnittet innehåller två metoder: readLock() och writeLock(). Läslåsningen kan hållas av flera trådar samtidigt, skrivlåsningen — endast av en. Ett typiskt exempel — en trådsäker cache: många trådar läser data och endast en uppdaterar dem periodiskt.

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 och optimistisk läsning

StampedLock lägger till ett tredje läge — tryOptimisticRead. Detta läge blockerar inte andra trådar, utan kommer bara ihåg en stämpel (stamp) av tillståndet. Efter läsning anropar utvecklaren validate(stamp) för att kontrollera om data ändrades under läsningen. Om data ändrades måste operationen upprepas.

Låsningar i mobil utveckling

I mobila applikationer används låsningar för att koordinera åtkomst till delad data mellan trådar. Deras användning kräver dock särskild försiktighet på grund av begränsade enhetsresurser och behovet av att bibehålla gränssnittets responsivitet.

Låsningar i Android (Kotlin)

På Android är ReentrantLock användbart vid arbete med Room, cacheminnen och filer. Det är viktigt att komma ihåg: lås aldrig på huvudtråden. För asynkron kod är korutiner och Mutex från kotlinx.coroutines att föredra, som inte blockerar tråden utan pausar korutinen.

Låsningar i iOS (Swift)

I iOS används standard Lock från NSLock mer sällan — utvecklare föredrar DispatchQueue med barrier-flaggor eller operativa låsningar os_unfair_lock. Swift 5.7+ tillhandahåller moderna synkroniseringsmekanismer via actors, som automatiskt skyddar tillståndet.

swift
import Foundation

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

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

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

Rekommendationer för att undvika deadlock

För att undvika deadlock, följ enhetlig låsningsordning för alla låsningar i projektet. Använd tryLock med timeout istället för lock() överallt där långvarig blockering är möjlig. Överväg att använda Lock-Free-algoritmer (AtomicReference, ConcurrentHashMap) istället för traditionella låsningar.

Bästa praxis för arbete med Lock

Användning av Lock kräver disciplin och efterlevnad av flera regler som förhindrar deadlock och prestandaförsämring. Denna praxis har utvecklats av Java-communityn under 20 års användning av paketet java.util.concurrent.

Frigivning i finally

Det viktigaste mönstret — lock i finally. Oavsett om den kritiska sektionen avslutades framgångsrikt eller med ett undantag, måste låsningen frigöras. Detta garanterar att andra trådar inte blockeras för evigt på grund av ett enda fel. I Kotlin löses detta mönster elegant genom tillägget withLock.

Minimera hålltiden

Den kritiska sektionen bör vara maximalt kort. Utför aldrig inmatning-utmatning, nätverksförfrågningar eller långa beräkningar inom låsningen. Om du behöver läsa data från servern, hämta dem först, lås sedan endast för att uppdatera delat tillstånd. Detta minskar konkurrensen och ökar systemets genomströmning.

Enhetlig låsningsordning

För att förhindra deadlock vid arbete med flera Lock, fastställ global låsningsordning i hela projektet. Om först lockA, sedan lockB låses — måste varje omvänd sekvens förbjudas av code review-regler. För automatisk kontroll, använd statiska analysatorer som SpotBugs och IntelliJ Inspections.

Vanliga frågor

Vad är skillnaden mellan Lock och synchronized?

Lock — explicit gränssnitt med möjlighet till timeout och avbrytbar väntan. synchronized låser och låser upp automatiskt, men tillåter inte användning av tryLock, lockInterruptibly och flera Condition. Lock är flexiblare, men kräver manuell frigivning i finally.

Vad är rättvis låsning (fair lock)?

Rättvis låsning garanterar FIFO-ordning för åtkomst: tråden som väntat längst får först låsningen. Orättvis låsning kan ge åtkomst till en ny tråd som kringgår kön, vilket ökar genomströmningen men kan orsaka svält för väntande trådar.

Hur undviker man deadlock vid användning av Lock?

Följ fast ordning för låsning av alla lås, använd tryLock med timeout istället för ovillkorlig lock och minimera antalet samtidigt hållna låsningar. Tillämpning av Lock-Free-datastrukturer minskar också risken för deadlock.

Vad är Condition i Lock?

Condition — motsvarigheten till wait/notify för Lock, som möjliggör organisering av flera oberoende vänteköer. Varje anrop av newCondition() skapar en separat kö, vilket ger mer precis kontroll över väckning av trådar jämfört med synchronizeds enda kö.

Vilket Lock välja för en mobil app?

För Android med korutiner, använd Mutex från kotlinx.coroutines — det pausar korutinen, inte tråden. För iOS med Swift 5.7+ är actors att föredra, som automatiskt synkroniserar åtkomst till tillståndet. Lämna ReentrantLock för äldre kod och lågnivåscenarier.

Sammanfattning

  • Lock — gränssnitt för explicit låsningshantering från java.util.concurrent.locks.
  • ReentrantLock — huvudimplementering med stöd för återlåsning och rättvisa.
  • ReadWriteLock delar upp låsningar i läsning och skrivning för read-heavy-scenarier.
  • StampedLock lägger till optimistisk läsning för maximal prestanda.
  • Timeout och Condition — viktigaste fördelarna med Lock jämfört med synchronized.
  • Deadlock förhindras med fast låsningsordning och användning av tryLock.
  • I mobil utveckling rekommenderas korutiner (Android) och actors (iOS).

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också