Ce face ReentrantLock
ReentrantLock protejează o bucată de cod (o „secțiune critică”) ca s-o ruleze un singur fir odată. lock() ia lacătul; dacă îl ține alt fir, așteaptă până se eliberează. unlock() eliberează lacătul pentru următorul fir.
Regula de aur: unlock() se scrie în blocul finally, imediat după lock(). Altfel, o excepție în zona protejată lasă lacătul blocat pentru totdeauna, iar celelalte fire așteaptă la nesfârșit.
„Reentrant” înseamnă că același fir poate lua lacătul de mai multe ori (de exemplu o metodă protejată apelează alta protejată de același lacăt); trebuie să-l elibereze de tot atâtea ori. Față de synchronized, oferă în plus tryLock, așteptare cu limită de timp și opțiunea de corectitudine (new ReentrantLock(true): firele intră în ordinea sosirii).
Ce returnează
lock() și unlock() nu returnează nimic. isLocked() și getHoldCount() dau starea lacătului.
Exemple
Exemplul 1: Un cont bancar folosit din mai multe fire
Patru fire fac câte 1000 de depuneri de 1 leu. Cu lacăt, soldul final este exact.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
static class Cont {
private final ReentrantLock lacat = new ReentrantLock();
private int sold = 0;
void depune(int suma) {
lacat.lock();
try {
sold = sold + suma;
} finally {
lacat.unlock();
}
}
int sold() {
lacat.lock();
try {
return sold;
} finally {
lacat.unlock();
}
}
}
public static void main(String[] args) {
Cont cont = new Cont();
try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
for (int i = 0; i < 4; i++) {
executor.submit(() -> {
for (int j = 0; j < 1000; j++) cont.depune(1);
});
}
}
System.out.println("Sold final: " + cont.sold() + " lei");
}
}
Rezultat
Sold final: 4000 lei
Exemplul 2: Același fir ia lacătul de două ori
import java.util.concurrent.locks.ReentrantLock;
public class Main {
static final ReentrantLock LACAT = new ReentrantLock();
static void salveaza() {
LACAT.lock();
try {
System.out.println("salveaza: getHoldCount = " + LACAT.getHoldCount());
valideaza();
} finally {
LACAT.unlock();
}
}
static void valideaza() {
LACAT.lock();
try {
System.out.println("valideaza: getHoldCount = " + LACAT.getHoldCount());
} finally {
LACAT.unlock();
}
}
public static void main(String[] args) {
salveaza();
System.out.println("La final, blocat: " + LACAT.isLocked());
}
}
Rezultat
salveaza: getHoldCount = 1
valideaza: getHoldCount = 2
La final, blocat: false
Exemplul 3: unlock în finally, chiar dacă apare o excepție
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
ReentrantLock lacat = new ReentrantLock();
try {
lacat.lock();
try {
Integer.parseInt("o sută");
} finally {
lacat.unlock();
}
} catch (NumberFormatException e) {
System.out.println("Eroare: " + e.getMessage());
}
System.out.println("Lacătul a rămas blocat? " + lacat.isLocked());
}
}
Rezultat
Eroare: For input string: "o sută"
Lacătul a rămas blocat? false
Greșeli frecvente
- Pui
unlock()la sfârșitul blocului, nu înfinally. La prima excepție lacătul rămâne luat și programul se blochează. - Apelezi
unlock()dintr-un fir care nu ține lacătul. AruncăIllegalMonitorStateException. - Pui
lock()în interiorul luitry. Dacălock()eșuează,finallyapeleazăunlock()fără lacăt; punelock()chiar înainte detry.
Comenzi înrudite
ReentrantLock.tryLock: Încearcă să ia lacătul fără să aștepte la nesfârșit: returnează true dacă l-a luat, false dacă este ocupat.AtomicInteger: Un int pe care mai multe fire îl pot modifica sigur, fără synchronized: get(), set(), addAndGet() și altele.ConcurrentHashMap: Un Map pe care mai multe fire îl pot citi și modifica în același timp, fără să se strice datele.CountDownLatch: Un numărător pentru așteptare: await() blochează până când alte fire apelează countDown() de N ori.