CodeGym /Blog Java /Aleatoriu /Multithreading în Java
John Squirrels
Nivel
San Francisco

Multithreading în Java

Publicat în grup
Bună! În primul rând, felicitări: ați ajuns la subiectul Multithreading în Java! Aceasta este o realizare serioasă - ați parcurs un drum lung. Dar pregătiți-vă: acesta este unul dintre cele mai dificile subiecte ale cursului. Și nu este că folosim clase complexe sau o mulțime de metode aici: de fapt, vom folosi mai puțin de douăzeci. Mai mult, va trebui să schimbi ușor modul în care gândești. Anterior, programele dumneavoastră au fost executate secvenţial. Unele linii de cod au venit după altele, unele metode au venit după altele și totul era practic clar. Mai întâi, am calculat ceva, apoi am afișat rezultatul pe consolă și apoi programul sa încheiat. Pentru a înțelege multithreading-ul, este mai bine să gândiți în termeni de paralelism. Să începem cu ceva destul de simplu: ) Imaginează-ți că familia ta se mută dintr-o casă în alta. Adunarea tuturor cărților va fi o parte importantă a mișcării. Ai acumulat o mulțime de cărți și trebuie să le pui în cutii. În prezent, ești singurul disponibil. Mama pregătește mâncarea, fratele împachetează hainele, iar sora a mers la magazin. Singur, te poți descurca cumva. Mai devreme sau mai târziu, vei finaliza singur sarcina, dar va dura mult timp. Totuși, sora ta se va întoarce de la magazin în 20 de minute și nu are altceva de făcut. Deci ea se poate alătura ție. Sarcina nu s-a schimbat: puneți cărțile în cutii. Dar se execută de două ori mai repede. De ce? Pentru că lucrarea se desfășoară în paralel. Două „file” diferite (tu și sora ta) îndeplinesc aceeași sarcină simultan. Și dacă nimic nu se schimbă, atunci va exista o diferență de timp uriașă în comparație cu situația în care faci totul de unul singur. Dacă fratele își termină treaba în curând, te poate ajuta și lucrurile vor merge și mai repede.

Probleme rezolvate prin multithreading

Multithreading a fost de fapt inventat pentru a atinge două obiective importante:
  1. Faceți mai multe lucruri în același timp.

    În exemplul de mai sus, diferite fire (membri ai familiei) au efectuat mai multe acțiuni în paralel: au spălat vase, au mers la magazin și au împachetat lucrurile.

    Putem oferi un exemplu mai strâns legat de programare. Să presupunem că aveți un program cu o interfață de utilizator. Când faceți clic pe „Continuare” în program, ar trebui să aibă loc unele calcule și utilizatorul ar trebui să vadă următorul ecran. Dacă aceste acțiuni ar fi efectuate secvențial, atunci programul s-ar bloca doar după ce utilizatorul face clic pe butonul „Continuare”. Utilizatorul va vedea ecranul cu ecranul butonului „Continuare” până când programul efectuează toate calculele interne și ajunge în partea în care interfața cu utilizatorul este reîmprospătată.

    Ei bine, cred că vom aștepta câteva minute!

    Multithreading în Java: ce este, beneficiile sale și capcanele comune - 3

    Sau ne-am putea reelabora programul sau, așa cum spun programatorii, să-l „paralelizăm”. Să ne efectuăm calculele pe un fir și să desenăm interfața cu utilizatorul pe altul. Majoritatea computerelor au suficiente resurse pentru a face acest lucru. Dacă luăm această rută, atunci programul nu se va îngheța, iar utilizatorul se va mișca fără probleme între ecrane, fără să-și facă griji cu privire la ceea ce se întâmplă în interior. Una nu interfereaza cu cealalta :)

  2. Efectuați calcule mai rapid.

    Totul este mult mai simplu aici. Dacă procesorul nostru are mai multe nuclee și majoritatea procesoarelor de astăzi au, atunci mai multe nuclee pot gestiona lista noastră de sarcini în paralel. Evident, dacă trebuie să îndeplinim 1000 de sarcini și fiecare durează o secundă, un nucleu poate termina lista în 1000 de secunde, două nuclee în 500 de secunde, trei în puțin mai mult de 333 de secunde etc.

Dar, așa cum ați citit deja în această lecție, sistemele de astăzi sunt foarte inteligente și chiar și pe un singur nucleu de calcul sunt capabile să atingă paralelism, sau mai degrabă pseudo-paralelism, unde sarcinile sunt efectuate alternativ. Să trecem generalitățile la detalii și să cunoaștem cea mai importantă clasă din biblioteca multithreading Java — java.lang.Thread. Strict vorbind, firele Java sunt reprezentate de instanțe ale clasei Thread . Aceasta înseamnă că pentru a crea și rula 10 fire, aveți nevoie de 10 instanțe ale acestei clase. Să scriem cel mai simplu exemplu:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
Pentru a crea și rula fire, trebuie să creăm o clasă, să o facem să moștenească java.lang . Thread clasa și suprascrieți metoda run() . Această ultimă cerință este foarte importantă. În metoda run() definim logica firului nostru de executat. Acum, dacă creăm și rulăm o instanță MyFirstThread , metoda run() va afișa o linie cu un nume: metoda getName() afișează numele „sistemului” firului, care este atribuit automat. Dar de ce vorbim provizoriu? Să creăm unul și să aflăm!

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Ieșire din consolă: eu sunt Thread! Numele meu este Thread-2 Sunt Thread! Numele meu este Thread-1 Sunt Thread! Numele meu este Thread-0 Sunt Thread! Numele meu este Thread-3 Sunt Thread! Numele meu este Thread-6 Sunt Thread! Numele meu este Thread-7 Sunt Thread! Numele meu este Thread-4 Sunt Thread! Numele meu este Thread-5 Sunt Thread! Numele meu este Thread-9, sunt Thread! Numele meu este Thread-8 Să creăm 10 fire ( obiecte MyFirstThread , care moștenesc Thread ) și să le începem apelând metoda start() pe fiecare obiect. După apelarea metodei start() , este executată logica metodei run() . Notă: numele firelor nu sunt în ordine. Este ciudat că nu au fost secvenţial:, Thread-1 , Thread-2 și așa mai departe? După cum se întâmplă, acesta este un exemplu de moment în care gândirea „secvențială” nu se potrivește. Problema este că am furnizat doar comenzi pentru a crea și a rula 10 fire. Planificatorul de fire, un mecanism special al sistemului de operare, decide ordinea lor de execuție. Designul său precis și strategia de luare a deciziilor sunt subiecte pentru o discuție profundă în care nu ne vom arunca acum. Principalul lucru de reținut este că programatorul nu poate controla ordinea de execuție a firelor. Pentru a înțelege gravitatea situației, încercați să rulați metoda main() din exemplul de mai sus de câteva ori. Ieșire din consolă la a doua rulare: Eu sunt Thread! Numele meu este Thread-0 Sunt Thread! Numele meu este Thread-4 Sunt Thread! Numele meu este Thread-3 Sunt Thread! Numele meu este Thread-2 Sunt Thread! Numele meu este Thread-1 Sunt Thread! Numele meu este Thread-5 Sunt Thread! Numele meu este Thread-6 Sunt Thread! Numele meu este Thread-8 Sunt Thread! Numele meu este Thread-9, sunt Thread! Numele meu este Thread-7 Console ieșire din a treia rulare: Sunt Thread! Numele meu este Thread-0 Sunt Thread! Numele meu este Thread-3 Sunt Thread! Numele meu este Thread-1 Sunt Thread! Numele meu este Thread-2 Sunt Thread! Numele meu este Thread-6 Sunt Thread! Numele meu este Thread-4 Sunt Thread! Numele meu este Thread-9, sunt Thread! Numele meu este Thread-5 Sunt Thread! Numele meu este Thread-7 Sunt Thread! Numele meu este Thread-8

Probleme create de multithreading

În exemplul nostru cu cărți, ați văzut că multithreading rezolvă sarcini foarte importante și poate face programele noastre mai rapide. Adesea de multe ori mai repede. Dar multithreading-ul este considerat a fi un subiect dificil. Într-adevăr, dacă este folosit necorespunzător, creează probleme în loc să le rezolve. Când spun „creează probleme”, nu mă refer într-un sens abstract. Există două probleme specifice pe care le poate crea multithreading: blocaj și condiții de cursă. Deadlock este o situație în care mai multe fire așteaptă resurse deținute unul de celălalt și niciunul dintre ele nu poate continua să ruleze. Vom vorbi mai multe despre asta în lecțiile ulterioare. Următorul exemplu va fi suficient deocamdată: Multithreading în Java: ce este, beneficiile sale și capcanele comune - 4Imaginează-ți că Thread-1 interacționează cu un obiect-1 și că Thread-2 interacționează cu Object-2. În plus, programul este scris astfel încât:
  1. Thread-1 încetează să interacționeze cu Object-1 și trece la Object-2 de îndată ce Thread-2 încetează să interacționeze cu Object-2 și trece la Object-1.
  2. Thread-2 încetează să interacționeze cu Object-2 și trece la Object-1 de îndată ce Thread-1 încetează să interacționeze cu Object-1 și trece la Object-2.
Chiar și fără o înțelegere profundă a multithreading-ului, puteți vedea cu ușurință că nu se va întâmpla nimic. Firele nu vor schimba niciodată locurile și se vor aștepta pentru totdeauna. Eroarea pare evidentă, dar în realitate nu este. Puteți face acest lucru cu ușurință într-un program. Vom lua în considerare exemple de cod care provoacă blocaj în lecțiile ulterioare. Apropo, Quora are un exemplu grozav din viața reală care explică ce impaseste. „În unele state din India, nu vă vor vinde terenuri agricole decât dacă sunteți fermier înregistrat. Cu toate acestea, nu te vor înregistra ca fermier dacă nu deții teren agricol'. Grozav! Ce putem spune?! :) Acum să vorbim despre condițiile de cursă. O condiție de cursă este o eroare de proiectare într-un sistem sau o aplicație cu mai multe fire, în care funcționarea sistemului sau a aplicației depinde de ordinea în care sunt executate părțile codului. Amintiți-vă, exemplul nostru în care am început firele:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Acum imaginați-vă că programul este responsabil pentru rularea unui robot care gătește mâncare! Thread-0 scoate ouăle din frigider. Thread-1 pornește aragazul. Thread-2 primește o tigaie și o pune pe aragaz. Thread-3 aprinde aragazul. Firul-4 toarnă ulei în tigaie. Firul-5 rupe ouăle și le toarnă în tigaie. Thread-6 aruncă cojile de ouă în coșul de gunoi. Thread-7 scoate ouăle fierte din arzător. Firul-8 pune ouăle fierte pe o farfurie. Thread-9 spală vasele. Uită-te la rezultatele programului nostru: Fire executată: Fire-0 Fire executată: Thread-2 Thread executată Thread-1 Thread executat: Thread-4 Thread executat: Thread-9 Thread executat: Thread-5 Thread executat: Thread-8 Thread executat: Thread-7 Thread executat: Thread-3 Este aceasta o rutină de comedie? :) Și totul pentru că munca programului nostru depinde de ordinea de execuție a firelor. Având în vedere cea mai mică încălcare a secvenței necesare, bucătăria noastră se transformă în iad, iar un robot nebun distruge totul în jurul ei. Aceasta este, de asemenea, o problemă comună în programarea cu mai multe fire. Veți auzi despre asta de mai multe ori. În încheierea acestei lecții, aș dori să recomand o carte despre multithreading. Multithreading în Java: ce este, beneficiile sale și capcanele comune - 6„Java Concurrency in Practice” a fost scris în 2006, dar nu și-a pierdut relevanța. Este dedicat programării Java multithreaded - de la elementele de bază până la cele mai comune greșeli și antimodeluri. Dacă într-o zi vă decideți să deveniți un guru multithreading, această carte este o citire obligatorie. Ne vedem la următoarele lecții! :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION