Bună! Când ați studiat multithreading pe CodeGym, ați întâlnit frecvent conceptele de „mutex” și „monitor”. Fără să arunci o privire, poți spune cum diferă? :) Dacă da, bravo! Dacă nu (acest lucru este cel mai frecvent), nu este o surpriză. „Mutex” și „monitor” sunt de fapt concepte legate. În plus, când citiți lecții și vizionați videoclipuri despre multithreading pe alte site-uri web, veți întâlni un alt concept similar: „semafor”. De asemenea, are o funcție foarte asemănătoare cu monitoarele și mutexurile. De aceea vom investiga acești trei termeni. Ne vom uita la câteva exemple și vom ajunge la o înțelegere definitivă a modului în care aceste concepte diferă unele de altele :)

Mutex

Un mutex (sau blocare) este un mecanism special pentru sincronizarea firelor. Unul este „atașat” la fiecare obiect în Java - știți deja că :) Nu contează dacă utilizați clase standard sau vă creați propriile clase, de exemplu Cat și Dog : toate obiectele tuturor claselor au un mutex . Termenul „mutex” provine de la „MUTual EXclusion”, care descrie perfect scopul său. După cum am spus într-una dintre lecțiile noastre anterioare, un mutex face posibil să ne asigurăm că doar un fir la un moment dat are acces la obiect. Un exemplu popular din viața reală de mutex implică toaletele. Când o persoană intră într-o toaletă despărțitoare, încuie ușa din interior. Toaleta este ca un obiect care poate fi accesat prin mai multe fire. Încuietoarea ușii despărțitoare este ca un mutex, iar șirul de oameni din afară reprezintă fire. Încuietoarea ușii este mutexul toaletei: asigură că o singură persoană poate intra înăuntru. Care este diferența dintre un mutex, un monitor și un semafor?  - 2Cu alte cuvinte, doar un fir la un moment dat poate funcționa cu resurse partajate. Încercările altor fire (oameni) de a obține acces la resursele ocupate vor eșua. Un mutex are mai multe caracteristici importante. În primul rând , sunt posibile doar două stări: „deblocat” și „blocat”. Acest lucru ne ajută să înțelegem cum funcționează: puteți trage paralele cu variabile booleene (adevărat/fals) sau numere binare (0/1). , statul nu poate fi controlat direct. Java nu are niciun mecanism care să vă permită să luați în mod explicit un obiect, să-i obțineți mutexul și să atribuiți starea dorită. Cu alte cuvinte, nu poți face ceva de genul:

Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
Aceasta înseamnă că nu puteți elibera mutex-ul unui obiect. Doar mașina Java are acces direct la ea. Programatorii lucrează cu mutexuri prin instrumentele limbajului.

Monitorizați

Un monitor este o „superstructură” suplimentară peste un mutex. De fapt, un monitor este o bucată de cod care este „invizibil” pentru programator. Când am vorbit mai devreme despre mutexuri, am dat un exemplu simplu:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
În blocul de cod marcat cu cuvântul cheie sincronizat , mutex-ul obiectului nostru obj este achiziționat. Grozav, putem achiziționa încuietoarea, dar cum anume este asigurată „protecția”? Când vedem cuvântul sincronizat , ce împiedică celelalte fire să intre în bloc? Protectia vine de la un monitor! Compilatorul convertește cuvântul cheie sincronizat în mai multe bucăți speciale de cod. Încă o dată, să revenim la exemplul nostru cu metoda doSomething() . Vom adăuga la el:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time
       synchronized (obj) {

           /* Do important work that requires that the object
           be accessed by only one thread */
           obj.someImportantMethod();
       }
   }
}
Iată ce se întâmplă „sub capotă” după ce compilatorul convertește acest cod:

public class Main {

   private Object obj = new Object();

   public void doSomething() throws InterruptedException {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time:
     
       /* as long as the object's mutex is busy,
       all the other threads (except the one that acquired it) are put to sleep */
       while (obj.getMutex().isBusy()) {
           Thread.sleep(1);
       }

       // Mark the object's mutex as busy
       obj.getMutex().isBusy() = true;

       /* Do important work that requires that the object
       be accessed by only one thread */
       obj.someImportantMethod();

       // Free the object's mutex
       obj.getMutex().isBusy() = false;
   }
}
Desigur, acesta nu este un exemplu real. Aici, am folosit cod asemănător Java pentru a descrie ceea ce se întâmplă în interiorul mașinii Java. Acestea fiind spuse, acest pseudo-cod oferă o înțelegere excelentă a ceea ce se întâmplă de fapt cu obiectul și firele din interiorul blocului sincronizat și modul în care compilatorul convertește acest cuvânt cheie în mai multe instrucțiuni care sunt „invizibile” pentru programator. Practic, Java folosește cuvântul cheie sincronizat pentru a reprezenta un monitor . Tot codul care apare în locul cuvântului cheie sincronizat din ultimul exemplu este monitorul.

Semafor

Un alt cuvânt pe care îl vei întâlni în studiul tău personal despre multithreading este „semafor”. Să ne dăm seama ce este acesta și cum diferă de un monitor și un mutex. Un semafor este un instrument pentru sincronizarea accesului la o anumită resursă. Caracteristica sa distinctivă este că folosește un contor pentru a crea mecanismul de sincronizare. Contorul ne spune câte fire pot accesa simultan resursa partajată. Care este diferența dintre un mutex, un monitor și un semafor?  - 3Semaforele în Java sunt reprezentate de clasa Semaphore . Când creăm obiecte semafor, putem folosi următorii constructori:

Semaphore(int permits)
Semaphore(int permits, boolean fair)
Transmitem urmatoarele constructorului:
    int permite — valoarea inițială și maximă a contorului. Cu alte cuvinte, acest parametru determină câte fire pot accesa simultan resursa partajată;
  • boolean fair — stabilește ordinea în care vor avea acces firele. Dacă fair este adevărat, atunci accesul este acordat firelor de execuție în așteptare în ordinea în care au solicitat-o. Dacă este fals, atunci ordinea este determinată de planificatorul de fire.
Un exemplu clasic de utilizare a semaforului este problema filosofului mesei. Care este diferența dintre un mutex, un monitor și un semafor?  - 4Pentru a facilita înțelegerea, o vom simplifica puțin. Imaginați-vă că avem 5 filozofi care trebuie să mănânce prânzul. În plus, avem o masă care poate găzdui simultan nu mai mult de două persoane. Sarcina noastră este să hrănim toți filozofii. Nici unul dintre ei nu ar trebui să-i fie foame și niciunul nu ar trebui să se „blocheze” unul pe celălalt atunci când încearcă să se așeze la masă (trebuie să evităm blocajul). Iată cum va arăta clasa noastră de filosofi:

class Philosopher extends Thread {

   private Semaphore sem;

   // Did the philosopher eat?
   private boolean full = false;

   private String name;

   Philosopher(Semaphore sem, String name) {
       this.sem=sem;
       this.name=name;
   }

   public void run()
   {
       try
       {
           // If the philosopher has not eaten
           if (!full) {
               // Ask the semaphore for permission to run
               sem.acquire();
               System.out.println(name + " takes a seat at the table");

               // The philosopher eats
               sleep(300);
               full = true;

               System.out.println(name + " has eaten! He leaves the table");
               sem.release();

               // The philosopher leaves, making room for others
               sleep(300);
           }
       }
       catch(InterruptedException e) {
           System.out.println("Something went wrong!");
       }
   }
}
Și iată codul pentru a rula programul nostru:

public class Main {

   public static void main(String[] args) {

       Semaphore sem = new Semaphore(2);
       new Philosopher(sem, "Socrates").start();
       new Philosopher(sem,"Plato").start();
       new Philosopher(sem,"Aristotle").start();
       new Philosopher(sem, "Thales").start();
       new Philosopher(sem, "Pythagoras").start();
   }
}
Am creat un semafor al cărui numărător este setat la 2 pentru a satisface condiția: doar doi filozofi pot mânca în același timp. Adică, doar două fire pot rula în același timp, deoarece clasa noastră Philosopher moștenește Thread ! Metodele acquire () și release() ale clasei Semaphore controlează contorul de acces. Metoda acquire() cere semaforului accesul la resursă. Dacă contorul este >0, atunci accesul este acordat și contorul este redus cu 1. Eliberarea ()metoda „eliberează” accesul acordat anterior, returnându-l la contor (mărește contorul de acces al semaforului cu 1). Ce obținem când rulăm programul? Este problema rezolvata? Nu se vor lupta filozofii noștri în timp ce își așteaptă rândul? :) Iată rezultatul consolei pe care l-am primit:

Socrates takes a seat at the table 
Plato takes a seat at the table 
Socrates has eaten! He leaves the table 
Plato has eaten! He leaves the table 
Aristotle takes a seat at the table 
Pythagoras takes a seat at the table 
Aristotle has eaten! He leaves the table 
Pythagoras has eaten! He leaves the table 
Thales takes a seat at the table 
Thales has eaten! He leaves the table 
Am reusit! Și deși Thales a trebuit să ia masa singur, nu cred că l-am jignit :) Poate că ați observat unele asemănări între un mutex și un semafor. Într-adevăr, au aceeași misiune: să sincronizeze accesul la o resursă. Care este diferența dintre un mutex, un monitor și un semafor?  - 5Singura diferență este că mutex-ul unui obiect poate fi achiziționat de un singur thread la un moment dat, în timp ce în cazul unui semafor, care folosește un contor de fire, mai multe fire pot accesa resursa simultan. Aceasta nu este doar o coincidență :) Un mutex este de fapt un semaforcu un număr de 1. Cu alte cuvinte, este un semafor care poate găzdui un singur fir. Este cunoscut și sub numele de „semafor binar” deoarece contorul său poate avea doar 2 valori — 1 („deblocat”) și 0 („blocat”). Asta este! După cum puteți vedea, până la urmă nu este atât de confuz :) Acum, dacă doriți să studiați mai detaliat multithreading pe Internet, vă va fi puțin mai ușor să navigați prin aceste concepte. Ne vedem la următoarele lecții!