MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Blocări - ReentrantLock

Pentru gestionarea accesului la o resursă comună, ca alternativă la operatorul synchronized, putem folosi blocările. Funcționalitatea blocărilor este inclusă în pachetul java.util.concurrent.locks.

La început, firul de execuție încearcă să acceseze resursa comună. Dacă aceasta este liberă, se aplică o blocare. După finalizarea lucrului, blocarea este eliberată de pe resursă. Dacă resursa nu este liberă și deja are o blocare aplicată, firul de execuție așteaptă până când blocarea este eliberată.

Clasele de blocări implementează interfața Lock, care definește următoarele metode:

  • void lock(): așteaptă până când se obține blocarea
  • void lockInterruptibly() throws InterruptedException: așteaptă până când se obține blocarea, dacă firul de execuție nu este întrerupt
  • boolean tryLock(): încearcă să obțină blocarea; dacă se obține, returnează true. Dacă nu, returnează false. Spre deosebire de metoda lock(), nu așteaptă obținerea blocării, dacă aceasta nu este disponibilă
  • void unlock(): eliberează blocarea
  • Condition newCondition(): returnează un obiect Condition, care este asociat cu blocarea curentă

Organizarea unei blocări, în general, este destul de simplă: pentru a obține blocarea se apelează metoda lock(), iar după ce lucrul cu resursele comune este încheiat, se apelează metoda unlock(), care eliberează blocarea.

Obiectul Condition permite gestionarea blocării.

De obicei, pentru lucrul cu blocări, se folosește clasa ReentrantLock din pachetul java.util.concurrent.locks. Această clasă implementează interfața Lock.

Pentru exemplu, vom lua codul din tema despre operatorul synchronized și vom rescrie acest cod folosind blocarea ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

public class Program {
 
   public static void main(String[] args) {
         
       CommonResource commonResource = new CommonResource();
       ReentrantLock locker = new ReentrantLock(); // creăm blocarea
       for (int i = 1; i < 6; i++) {
             
           Thread t = new Thread(new CountThread(commonResource, locker));
           t.setName("Thread " + i);
           t.start();
       }
   }
}
 
class CommonResource {
   int x = 0;
}
 
class CountThread implements Runnable {
 
   CommonResource res;
   ReentrantLock locker;
   CountThread(CommonResource res, ReentrantLock lock) {
       this.res = res;
       locker = lock;
   }
   public void run() {
       
       locker.lock(); // aplicăm blocarea
       try {
           res.x = 1;
           for (int i = 1; i < 5; i++) {
               System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);
               res.x++;
               Thread.sleep(100);
           }
       }
       catch (InterruptedException e) {
           System.out.println(e.getMessage());
       }
       finally {
           locker.unlock(); // eliberăm blocarea
       }
   }
}

Aici se folosește și resursa comună CommonResource, pentru gestionarea căreia se creează cinci fire de execuție. La intrarea în secțiunea critică se aplică blocarea:

locker.lock();

După aceasta, doar un fir de execuție are acces la secțiunea critică, iar celelalte fire așteaptă eliberarea blocării. În blocul finally, după ce firul de execuție și-a încheiat activitatea principală, blocarea este eliberată. Acest lucru se face neapărat în blocul finally, deoarece în cazul apariției unei erori, toate celelalte fire ar rămâne blocate.

În final, vom obține un rezultat similar cu cel obținut în cazul folosirii operatorului synchronized:

Thread 4 1 
Thread 4 2 
Thread 4 3 
Thread 4 4 
Thread 3 1 
Thread 3 2 
Thread 3 3 
Thread 3 4 
Thread 2 1 
Thread 2 2 
Thread 2 3 
Thread 2 4 
Thread 1 1 
Thread 1 2 
Thread 1 3 
Thread 1 4 
Thread 5 1 
Thread 5 2 
Thread 5 3 
Thread 5 4
← Lecția anterioară Lecția următoare →