MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Semafoare

Semafoarele reprezintă un alt mijloc de sincronizare pentru accesul la resurse. În Java, semafoarele sunt reprezentate de clasa Semaphore, care face parte din pachetul java.util.concurrent.

Pentru a gestiona accesul la o resursă, semaforul folosește un contor care reprezintă numărul de permisiuni disponibile. Dacă valoarea contorului este mai mare decât zero, atunci thread-ul primește acces la resursă, iar contorul scade cu o unitate.

După ce thread-ul termină de lucrat cu resursa, eliberează semaforul, iar contorul crește din nou cu o unitate. Dacă contorul este zero, thread-ul se blochează și așteaptă până când semaforul îi va acorda permisiunea.

Numărul de permisiuni pentru accesul la resursă poate fi stabilit folosind constructorii clasei Semaphore:

Semaphore(int permits)
Semaphore(int permits, boolean fair)

Parametrul permits specifică numărul de permisiuni admise pentru accesul la resursă. Parametrul fair din cel de-al doilea constructor permite stabilirea ordinii de acces. Dacă acesta este true, permisiunile vor fi acordate thread-urilor care au solicitat accesul în ordinea cererii.

Dacă este false, permisiunile vor fi acordate în ordine nedeterminată.

Pentru a obține permisiunea de la semafor, trebuie să apelăm metoda acquire(), care are două forme:

void acquire() throws InterruptedException
void acquire(int permits) throws InterruptedException

Prima variantă este utilizată pentru a obține o permisiune, iar cea de-a doua pentru a obține mai multe permisiuni. După apelarea acestei metode, thread-ul se blochează până primește permisiunea.

După ce a terminat de lucrat cu resursa, permisiunea obținută anterior trebuie eliberată folosind metoda release():

void release()
void release(int permits)

Prima variantă eliberează o permisiune, iar a doua variantă eliberează un număr de permisiuni specificat prin parametrul permits.

Vom folosi un semafor într-un exemplu simplu:

import java.util.concurrent.Semaphore;

public class Program {

   public static void main(String[] args) {
       
       Semaphore sem = new Semaphore(1); // 1 permisiune
       CommonResource res = new CommonResource();
       new Thread(new CountThread(res, sem, "CountThread 1")).start();
       new Thread(new CountThread(res, sem, "CountThread 2")).start();
       new Thread(new CountThread(res, sem, "CountThread 3")).start();
   }
}

class CommonResource {
   int x = 0;
}

class CountThread implements Runnable {

   CommonResource res;
   Semaphore sem;
   String name;
   CountThread(CommonResource res, Semaphore sem, String name) {
       this.res = res;
       this.sem = sem;
       this.name = name;
   }
   
   public void run() {
       
       try {
           System.out.println(name + " așteaptă permisiunea");
           sem.acquire();
           res.x = 1;
           for (int i = 1; i < 5; i++) {
               System.out.println(this.name + ": " + res.x);
               res.x++;
               Thread.sleep(100);
           }
       } catch (InterruptedException e) {
           System.out.println(e.getMessage());
       }
       System.out.println(name + " eliberează permisiunea");
       sem.release();
   }
}

Aici avem o resursă comună CommonResource cu un câmp x, care este modificat de fiecare thread. Thread-urile sunt reprezentate de clasa CountThread, care obține semaforul și efectuează anumite operații asupra resursei. În clasa principală a programului, aceste thread-uri sunt lansate. Rezultatul va fi următorul:

CountThread 1 așteaptă permisiunea
CountThread 2 așteaptă permisiunea
CountThread 3 așteaptă permisiunea
CountThread 1: 1
CountThread 1: 2
CountThread 1: 3
CountThread 1: 4
CountThread 1 eliberează permisiunea
CountThread 3: 1
CountThread 3: 2
CountThread 3: 3
CountThread 3: 4
CountThread 3 eliberează permisiunea
CountThread 2: 1
CountThread 2: 2
CountThread 2: 3
CountThread 2: 4
CountThread 2 eliberează permisiunea

Semafoarele sunt foarte utile pentru rezolvarea problemelor în care trebuie să se limiteze accesul. Un exemplu clasic este problema "filozofilor la masă". Să presupunem că există mai mulți filozofi (de exemplu, cinci), dar simultan la masă pot sta doar doi. Trebuie ca toți filozofii să mănânce fără a crea blocaje reciproce în lupta pentru tacâmuri și farfurii:

import java.util.concurrent.Semaphore;

public class Program {

   public static void main(String[] args) {
       
       Semaphore sem = new Semaphore(2);
       for (int i = 1; i < 6; i++)
           new Philosopher(sem, i).start();
   }
}

// Clasa filozofului
class Philosopher extends Thread {
   Semaphore sem; // semaforul care limitează numărul de filozofi
   int num = 0; // numărul de mese
   int id; // numărul filozofului
   
   Philosopher(Semaphore sem, int id) {
       this.sem = sem;
       this.id = id;
   }
   
   public void run() {
       try {
           while (num < 3) { // până când filozoful nu a mâncat de 3 ori
               sem.acquire();
               System.out.println("Filozoful " + id + " se așază la masă");
               // filozoful mănâncă
               sleep(500);
               num++;
               
               System.out.println("Filozoful " + id + " pleacă de la masă");
               sem.release();
               
               // filozoful se plimbă
               sleep(500);
           }
       } catch (InterruptedException e) {
           System.out.println("Filozoful " + id + " are probleme de sănătate");
       }
   }
}

În final, doar doi filozofi vor putea sta la masă simultan, iar ceilalți vor aștepta:

Filozoful 1 se așază la masă
Filozoful 3 se așază la masă
Filozoful 3 pleacă de la masă
Filozoful 1 pleacă de la masă
Filozoful 2 se așază la masă
Filozoful 4 se așază la masă
Filozoful 2 pleacă de la masă
Filozoful 4 pleacă de la masă
Filozoful 5 se așază la masă
Filozoful 1 se așază la masă
Filozoful 1 pleacă de la masă
Filozoful 5 pleacă de la masă

← Lecția anterioară Lecția următoare →