Multithreading în java

În acest articol voi atinge tema mare și complexă a multithreading în Java. Desigur, nu pot vorbi despre totul într-un singur articol, așa că mă voi referi doar la subiectele cele mai de bază.







Avantajele multithreading
În ciuda multor probleme, multithreading este foarte util în interfețele utilizatorilor. În timp ce un fir este implicat în procesarea datelor, al doilea este desenul grafic în liniște. Ca rezultat, vedem o animație netedă pe ecran și putem interacționa cu elementele de interfață, fără să ne temem că totul se va încheia.

Multithreading îmbunătățește, de asemenea, viteza de procesare: în timp ce o pregătește fluxul de date, de exemplu, de pompare de pe Internet în porții mici, cursuri de apă doilea și al treilea le poate procesa și înregistra rezultatul în a patra unitate.

Dacă avem un procesor multi-core, firele vor îmbunătăți semnificativ performanța prin mutarea lucrării în alte nuclee.


Ei bine, suficientă teorie, să mergem mai departe să practicăm.


Crearea de fire
În Java, clasa Thread este utilizată pentru lucrul cu fire. Există două moduri de a crea un fir nou, dar le puteți scrie în mai multe moduri.

Prima opțiune este o extensie a clasei Thread.

clasa publica CustomThread extinde Thread # 123;

Sistem. out. println # 40; 6 # 41; ;


Puteți totuși să vă răsfățați cu API Reflection, dar este ca și dumneavoastră.


Întreruperea firelor
Da, este o întrerupere, nu opriți sau întrerupeți. Fluxurile nu pot fi oprite prin mijloace standard, deoarece există multe probleme în acest sens. Prin urmare, oprirea, suspendarea, întreruperea și distrugerea metodelor din clasa Thread sunt marcate ca fiind depreciate. Deci trebuie să te oprești.

În general, un thread în Java funcționează atâta timp cât este executat codul în metoda sa de rulare. Dacă rularea este o operație simplă, să zicem, ieșirea textului în consola, firul se va termina imediat după această operație. Dacă într-un flux scrieți datele într-un ciclu în 100 de fișiere, fluxul va funcționa până când nu va îndeplini sarcina.

Și, apropo, dacă firul rulează în fundal și închideți programul, atunci procesul acestui program va rămâne în fundal, până când toate firele vor termina. Pentru a vă asigura că după terminarea programului este garantată finalizarea firelor, trebuie să o faceți din firul demonic. Suna amuzant, dar este adevarat:

fir. setDaemon # 40; adevărat # 41;


Deci, avem un fir care face o operație repetitivă în buclă și trebuie să o terminăm la un moment dat. Modul cel mai evident este de a crea o variabilă booleană și de a verifica starea acesteia. De cele mai multe ori faceți acest lucru:

boolean privat esteRunning;

Toate firele au terminat. Counter = 5000

Cum va funcționa:
- Am creat două fire și le-am fugit.
- Să spunem că primul fir a început mai repede și a intrat în buclă. Al doilea este încă în desfășurare.
- Primul fir vede blocul sincronizat. Se efectuează un control - sunt alte fire în acest bloc acum? Nu, deci primul fir intră în bloc. Cel de-al doilea a introdus până acum buclele.
- Primul fir care se află acum în buclă pentru forțează contorul. Al doilea fir ajunge la blocul sincronizat. Din nou, verificarea se efectuează și din moment ce există un fir interior, permisiunea de a intra nu este primită, astfel că al doilea fir este în așteptare.
- Primul fir este încă în buclă pentru. Al doilea fir încă mai așteaptă.
- În cele din urmă, primul fir iese din buclă for și părăsește zona de sincronizare. Al doilea fir primește permisiunea de a intra înăuntru.

Astfel, se obține lucrarea sincronizată a fluxurilor.


Blocurile de sincronizare ar trebui plasate cu înțelepciune. Dacă am făcut acest lucru:

privat static void printCounter # 40; # 41; # 123;

sincronizate # 40; Counter. clasă # 41; # 123;

în timp ce # 40; Counter. obține # 40; # 41; <5000 ) {

pentru # 40; int i = 0; eu <10 ; i ++ ) {

Counter. creștere # 40; # 41; ;

Subiect. somn # 40; 1 # 41; ;

# 125; captură # 40; Interpretată Excepție ex # 41; # 123;

Subiect. currentThread # 40; # 41;. întrerupe # 40; # 41; ;


am obține, de asemenea, rezultatul corect de 5000, care este doar de lucru am avea un singur fir:
- Creați două fire și rulați-le.
- Să spunem că primul fir a început mai repede și a intrat în blocul de sincronizare. Al doilea este încă în desfășurare.






- Primul fir este acum în buclă. Al doilea fir a întâlnit blocul sincronizat și nu a primit permisiunea de a se conecta.
- Primul thread funcționează. Al doilea este în așteptare.
- După un timp, primul fir a mărit contorul la 5000 și a lăsat ciclurile și blocul de sincronizare. Al doilea flux este permis să intre în interior.
- Primul fir a fost finalizat. Al doilea fir a verificat faptul că condiția Counter.get () <5000 уже не выполняется и не вошёл в цикл while. Покинул блок синхронизации и завершился.


O altă modalitate de a rezolva problema cu contorul este de a face sincronizarea metodelor sale de obținere și incrementare. Apoi blocul de sincronizare din metoda de rulare nu este necesar.

public class SynchronizedCounter # 123;

privat static int contra = 0;


Adică mergem la unitatea de sincronizare, monitorizăm. Returnați valoarea și ieșiți din monitor. Dacă se produce o excepție în timpul secțiunii critice, ieșim și din monitor.

În metoda bytecode sincronizate, monitorizatorul și monitororexitul nu existau, dar acest lucru nu înseamnă că nu există nici o intrare pentru monitor. Flagul SYNCHRONIZED al metodei indică JVM că toate aceste instrucțiuni trebuie efectuate. Adică, ele nu apar în cod, dar sunt ascunse în JVM - le va executa în continuare.


Privind în perspectivă, voi demonstra o altă soluție posibilă a problemei. În pachetul java.util.concurrent, există mai multe clase pentru diferite necesități multi-threaded. O astfel de clasă este AtomicInteger. care face operațiuni pe numere atomice.

clasa publica AtomicCounter # 123;

privat static final AtomicInteger counter = new AtomicInteger # 40; # 41; ;

public static int get # 40; # 41; # 123;

return counter. obține # 40; # 41; ;

public voce increment static # 40; # 41; # 123;

contor. incrementAndGet # 40; # 41; ;


Nicăieri nu este necesar să se adauge un bloc de sincronizare.
Voi încerca să vorbesc despre alte clase care simplifică lucrarea cu multithreading în următorul articol.


Sincronizarea. Exemplu de proiectare a unei aplicații cu mai multe fire
Pentru a-mi rezolva problema, vreau să arăt un exemplu de aplicație mică și importanța designului corect al firului.
Să presupunem că avem 20 de fișiere de date. Trebuie să citiți cele mai eficiente fișiere și să afișați datele de pe ecran. Datele, după ce ați citit, vor fi adăugate în listă și vor fi afișate de acolo.

Există o clasă responsabilă pentru panoul de desen, acolo vom adăuga obiecte în timp ce citim:

panoul privat PaintPanel;

private void processFile # 40; Fișier fișier # 41; # 123;

Sistem. out. printf # 40; "Fișier de proces% s în% s thread \ n". fișier. getName # 40; # 41;. Subiect. currentThread # 40; # 41;. getName # 40; # 41; # 41; ;

încerca # 40; FileInputStream fis = nou FileInputStream # 40; fișier # 41; ;

DataInputStream dis = nou DataInputStream # 40; fls # 41; # 41; # 123;

în timp ce # 40; Dis. disponibil # 40; # 41;> 0 # 41; # 123;

Triunghi triunghi = triunghi. citit # 40; DIS # 41; ;

panou. addTriangle # 40; triunghi # 41; ;

Subiect. somn # 40; 1 # 41; ;

# 125; captură # 40; IOException | Interpretată ieșire ie # 41; # 123;


Acum nu există erori, dar prelucrarea a 20 de fișiere durează aproximativ cinci minute. Mai mult, primele fișiere sunt citite rapid, iar apoi munca încetinește. Încercați să înțelegeți de ce se întâmplă acest lucru.


▌ Opțiunea 2. Un fișier - un flux

Având 20 de fire, este logic să presupunem că munca va fi de 20 de ori mai rapidă. Deci, ar fi, avem procesorul nostru cel puțin 20 nuclee. În caz contrar, vom crea doar o sarcină suplimentară și toate datele noastre nu pot fi extrase foarte ușor.

listă fire = noul ArrayList <> # 40; fișiere. lungime # 41; ;

pentru # 40; Fișier fișier. fișiere # 41; # 123;

Thread thread = nou subiect # 40; # 40; # 41; -> processFile # 40; fișier # 41; # 41; ;

fire. adăuga # 40; fir # 41; ;

fir. start # 40; # 41; ;

pentru # 40; File de filet. fire # 41; # 123;

fir. alătura # 40; # 41; ;

# 125; captură # 40; Interpretatăexcepție e # 41; # 123;

Subiect. currentThread # 40; # 41;. întrerupe # 40; # 41; ;


Cu toate acestea, timpul de lucru este acum: 40 de secunde. E mult timp. Aceeași problemă care a încetinit activitatea în prima versiune încetinește totul acum. Trebuie să existe o modalitate de a scăpa de blocurile sincronizate și astfel există.


▌ Opțiunea 3: Utilizați lista sincronizată

Pentru a face o listă sincronizată dintr-o listă regulată, vom apela metoda

Colecții. synchronizedList # 40; listă # 41;


Acum vom primi o listă, din care toate metodele vor fi sincronizate. Dar, din păcate, iteratorul utilizat în foreach nu va fi sincronizat în acest fel și va trebui fie să îl înfășurăm într-un bloc sincronizat, fie să îl abandonăm în favoarea pur și simplu enumerând elementele din buclă pentru.

clasa publica PaintPanelSynchronizedList extinde PaintPanel # 123;

lista finală privată triunghiuri;

public PaintPanelSynchronizedList # 40; int lățime, int înălțime # 41; # 123;

super # 40; lățime, înălțime # 41; ;

triangles = Colecții. synchronizedList # 40; noul ArrayList <> # 40; # 41; # 41; ;


▌ Opțiunea 4. Limitați numărul de fire

Putem limita numărul de fire create prin crearea unui bazin:

ExecutorService es = Executori. newFixedThreadPool # 40; maxThreads # 41; ;


Acum puteți crea cel puțin o sută de fire, în același timp nu vor exista mai mult decât fluxurile maxThreads.

Limitați piscina la cinci fire

ExecutorService es = Executori. newFixedThreadPool # 40; 5 # 41; ;

pentru # 40; Fișier fișier. fișiere # 41; # 123;

es. executa # 40; # 40; # 41; -> processFile # 40; fișier # 41; # 41; ;

es. închidere # 40; # 41; ;

es. awaitTermination # 40; 2. TimeUnit. mINUTE # 41; ;

# 125; captură # 40; Interpretatăexcepție e # 41; # 123;

Subiect. currentThread # 40; # 41;. întrerupe # 40; # 41; ;


Acum, timpul de execuție a crescut la 11 secunde, dar redarea are loc lin și putem fi siguri că dacă am avea sute de fișiere, sistemul nu primește o sarcină mare.

Cea mai bună practică este limitarea numărului de thread-uri cu numărul de procesoare din sistem:

Executori. newFixedThreadPool # 40; Runtime. getRuntime # 40; # 41;. availableProcessors # 40; # 41; # 41; ;


▌ Opțiunea 5. java.util.concurrent







Articole similare

Trimiteți-le prietenilor: