Lock: ce este, tipuri de blocări și utilizare în sincronizare

Autor: IT Sectr Publicat: 2026-03-19 Timp de citire: 8 min

Lock este un mecanism de sincronizare care asigură accesul exclusiv la secțiunile critice ale codului în aplicațiile multithreading. Conform Oracle, 2024, interfața Lock oferă un control mai flexibil al sincronizării comparativ cu blocurile synchronized tradiționale, inclusiv încercări de capturare cu timeout și suport pentru mai multe cozi de așteptare.

Principalele puncte

  • Lock — interfață pentru gestionarea explicită a blocărilor în Java.
  • ReentrantLock — implementarea de bază cu suport pentru re-capturare de către același fir.
  • ReadWriteLock separă blocările în citire și scriere pentru creșterea performanței.
  • Deadlock — riscul principal la utilizarea mai multor blocări simultan.
  • Spre deosebire de synchronized, Lock suportă timeouturi și așteptare întreruptibilă.

Ce este Lock?

Lock este o interfață din pachetul java.util.concurrent.locks care oferă operațiuni explicite de capturare și eliberare pentru sincronizarea accesului la date. Spre deosebire de synchronized, Lock oferă dezvoltatorului control complet asupra mecanismului de blocare.

Definiție și rol în sincronizare

Interfața Lock a apărut în Java 5 ca alternativă la mecanismul încorporat synchronized. Metodele principale sunt lock, unlock, tryLock și lockInterruptibly. Blocările permit organizarea accesului sigur la date într-un mediu multithreading, prevenind condițiile de cursă și deteriorarea datelor.

Principalul avantaj al Lock față de synchronized este flexibilitatea. Dezvoltatorul poate încerca să captureze blocarea cu timeout, poate verifica ocuparea acesteia fără blocare sau poate organiza mai multe cozi de așteptare cu priorități diferite.

Istoria dezvoltării

Înainte de apariția interfeței Lock în Java 5, singura modalitate de sincronizare era synchronized, care suferea de limitări: lipsa timeouturilor, imposibilitatea întreruperii așteptării și o singură coadă. Doug Lea a proiectat pachetul java.util.concurrent, incluzând Lock ca bloc fundamental de construcție.

Cum funcționează blocarea?

Blocarea gestionează accesul printr-un steag intern de stare și o coadă de așteptare. Când un fir apelează lock(), mecanismul verifică dacă blocarea este liberă și fie o capturează, fie plasează firul în coadă până la eliberare.

Capturarea și eliberarea atomică

La baza oricărei blocări se află o operațiune atomică de comparare și setare (CAS). La apelul lock(), firul încearcă să seteze atomic steagul de ocupare. Dacă steagul este deja setat, firul este blocat. La unlock(), steagul este resetat și unul dintre firele în așteptare este trezit.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // secțiune critică
        println("Funcționează firul ${Thread.currentThread().name}")
    } finally {
        lock.unlock()
    }
}

Coada de așteptare și trezirea

ReentrantLock folosește intern o coadă bidirecțională (CLH lock queue), unde fiecare fir în așteptare este reprezentat de un nod. Când blocarea este eliberată, nodul principal al cozii este trezit. Modul fair (corect) garantează ordinea FIFO, iar unfair permite capturarea de către un fir nou înaintea celor în așteptare pentru creșterea debitului.

Tipuri principale de blocări

În stiva modernă Java există mai multe implementări de blocări, fiecare optimizată pentru scenarii specifice. Alegerea blocării corecte influențează direct performanța și fiabilitatea aplicației multithreading.

ReentrantLock

ReentrantLock — implementarea de bază și cea mai frecvent utilizată a Lock. Suportă re-capturarea de către același fir: dacă firul deține deja blocarea, un apel repetat lock() nu îl blochează. Aceasta previne deadlock-ul în apelurile recursive.

ReentrantReadWriteLock

ReadWriteLock separă blocările în două moduri: citire și scriere. Mai multe fire pot deține simultan blocarea de citire, dar scrierea necesită acces exclusiv. Aceasta crește semnificativ performanța la citire frecventă și scriere rară.

StampedLock

StampedLock — cea mai nouă implementare, apărută în Java 8. Suportă trei moduri: scriere, citire și citire optimistă. Citirea optimistă nu blochează alte fire și verifică validitatea datelor după citire, oferind o creștere a performanței de 10-20% comparativ cu ReadWriteLock.

BlocareVersiune JavaModuriPerformanță
ReentrantLockJava 5exclusivridicată
ReadWriteLockJava 5citire + scrieremedie
StampedLockJava 8citire + scriere + optimisticfoarte ridicată

ReentrantLock și caracteristicile sale

ReentrantLock — cea mai populară implementare a Lock, care oferă o serie de capacități indisponibile în synchronized. Înțelegerea caracteristicilor sale este necesară pentru lucrul eficient cu multithreading-ul.

Corectitudinea blocării (fairness)

Constructorul ReentrantLock acceptă parametrul fair. La true, blocarea garantează ordinea FIFO de acces, la false este posibilă capturarea de către un fir nou înaintea celor în așteptare. Modul corect previne înfometarea, dar reduce debitul cu 10-20% din cauza cheltuielilor suplimentare pentru menținerea cozii.

Timeouturi și așteptare întreruptibilă

Spre deosebire de synchronized, ReentrantLock suportă tryLock cu timeout. Dacă blocarea nu a putut fi capturată în timpul specificat, firul continuă execuția, în loc să se blocheze la nesfârșit. Metoda lockInterruptibly permite întreruperea firului în așteptare prin Thread.interrupt().

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Blocare capturată")
        } finally {
            lock.unlock()
        }
    } else {
        println("Nu s-a putut captura blocarea")
    }
}

Condiții (Conditions)

ReentrantLock suportă mai multe variabile condiționale prin metoda newCondition(). Fiecare Condition are propria coadă de așteptare, permițând organizarea unor scenarii complexe de trezire. Metodele await() și signal() au înlocuit wait() și notify() din blocurile synchronized, dar cu suport pentru mai multe cozi.

ReadWriteLock și StampedLock

ReadWriteLock și StampedLock rezolvă problema optimizării accesului când operațiile de citire predomină asupra scrierii. Ele sunt semnificativ mai eficiente decât ReentrantLock în scenariile unde citirea are loc mai des decât scrierea.

ReadWriteLock în practică

Interfața ReadWriteLock conține două metode: readLock() și writeLock(). Blocarea de citire poate fi deținută simultan de mai multe fire, blocarea de scriere — doar de unul singur. Un exemplu tipic — un cache thread-safe: mai multe fire citesc date și doar unul le actualizează periodic.

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 și citirea optimistă

StampedLock adaugă un al treilea mod — tryOptimisticRead. Acest mod nu blochează alte fire, ci doar memorează un marcaj (stamp) al stării. După citire, dezvoltatorul apelează validate(stamp) pentru a verifica dacă datele s-au modificat în timpul citirii. Dacă datele s-au modificat, operația trebuie repetată.

Blocări în dezvoltarea mobilă

În aplicațiile mobile, blocările sunt utilizate pentru coordonarea accesului la datele partajate între fire. Cu toate acestea, utilizarea lor necesită o atenție deosebită din cauza resurselor limitate ale dispozitivului și a necesității de a menține receptivitatea interfeței.

Blocări în Android (Kotlin)

Pe Android, ReentrantLock este util la lucrul cu Room, cache-uri și fișiere. Este important de reținut: nu capturați niciodată blocarea pe firul principal. Pentru cod asincron, sunt preferate corutinele și Mutex din kotlinx.coroutines, care nu blochează firul, ci suspendă corutina.

Blocări în iOS (Swift)

În iOS, Lock-ul standard din NSLock este folosit mai rar — dezvoltatorii preferă DispatchQueue cu flag-uri barrier sau blocările operaționale os_unfair_lock. Swift 5.7+ oferă mecanisme moderne de sincronizare prin actors, care protejează automat starea.

swift
import Foundation

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

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

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

Recomandări pentru evitarea deadlock-ului

Pentru a evita deadlock-ul, respectați ordinea unică de capturare a tuturor blocărilor din proiect. Folosiți tryLock cu timeout în loc de lock() oriunde unde este posibilă blocarea pe termen lung. Luați în considerare aplicarea algoritmilor Lock-Free (AtomicReference, ConcurrentHashMap) în locul blocărilor tradiționale.

Cele mai bune practici de lucru cu Lock

Utilizarea Lock necesită disciplină și respectarea mai multor reguli care previn deadlock-ul și căderea performanței. Aceste practici au fost elaborate de comunitatea Java în 20 de ani de utilizare a pachetului java.util.concurrent.

Eliberarea în finally

Cel mai important tipar — lock în finally. Indiferent dacă secțiunea critică s-a încheiat cu succes sau cu o excepție, blocarea trebuie eliberată. Aceasta garantează că alte fire nu vor fi blocate permanent din cauza unei singure erori. În Kotlin, acest tipar este elegant rezolvat prin extensia withLock.

Minimizarea timpului de deținere

Secțiunea critică trebuie să fie maxim de scurtă. Nu executați niciodată în interiorul blocării operațiuni de intrare-ieșire, cereri de rețea sau calcule lungi. Dacă trebuie să citiți date de pe server, mai întâi obțineți-le, apoi capturați blocarea doar pentru actualizarea stării partajate. Aceasta reduce competiția și crește debitul sistemului.

Ordinea unică de capturare

Pentru prevenirea deadlock-ului la lucrul cu mai multe Lock, fixați ordinea globală de capturare în întregul proiect. Dacă mai întâi se capturează lockA, apoi lockB — orice secvență inversă trebuie interzisă prin regulile de code review. Pentru verificare automată, folosiți analizoare statice precum SpotBugs și IntelliJ Inspections.

Întrebări frecvente

Care este diferența dintre Lock și synchronized?

Lock — o interfață explicită cu posibilitatea de timeout și așteptare întreruptibilă. synchronized capturează și eliberează automat monitorul, dar nu permite utilizarea tryLock, lockInterruptibly și a mai multor Condition. Lock este mai flexibil, dar necesită eliberare manuală în finally.

Ce este o blocare corectă (fair lock)?

Blocarea corectă garantează ordinea FIFO de acces: firul care așteaptă cel mai mult primește primul blocarea. Blocarea incorectă poate oferi accesul unui fir nou ocolind coada, ceea ce crește debitul, dar poate cauza înfometarea firelor în așteptare.

Cum să evit deadlock-ul la utilizarea Lock?

Respectați ordinea fixă de capturare a tuturor blocărilor, folosiți tryLock cu timeout în loc de lock necondiționat și minimizați numărul de blocări deținute simultan. Aplicarea structurilor de date Lock-Free reduce, de asemenea, riscul de deadlock.

Ce este Condition în Lock?

Condition — analogul wait/notify pentru Lock, care permite organizarea mai multor cozi de așteptare independente. Fiecare apel newCondition() creează o coadă separată, oferind un control mai precis asupra trezirii firelor comparativ cu o singură coadă synchronized.

Ce Lock să aleg pentru o aplicație mobilă?

Pentru Android cu corutine, folosiți Mutex din kotlinx.coroutines — acesta suspendă corutina, nu blochează firul. Pentru iOS cu Swift 5.7+, sunt preferați actors, care sincronizează automat accesul la stare. ReentrantLock păstrați-l pentru codul vechi și scenariile de nivel scăzut.

Rezumat

  • Lock — interfață de gestionare explicită a blocărilor din java.util.concurrent.locks.
  • ReentrantLock — implementarea principală cu suport pentru re-capturare și corectitudine.
  • ReadWriteLock separă blocările în citire și scriere pentru scenarii read-heavy.
  • StampedLock adaugă citirea optimistă pentru performanță maximă.
  • Timeouturile și Condition — avantajele cheie ale Lock față de synchronized.
  • Deadlock se previne prin ordinea fixă de capturare și utilizarea tryLock.
  • În dezvoltarea mobilă sunt recomandate corutinele (Android) și actors (iOS).

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și