Probleme rezolvate prin multithreading
Multithreading a fost de fapt inventat pentru a atinge două obiective importante:-
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!
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 :)
-
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.
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ă:
- 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.
- 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.
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. 
GO TO FULL VERSION