Introducere
Firele sunt un lucru interesant. În recenziile anterioare, ne-am uitat la unele dintre instrumentele disponibile pentru implementarea multithreading-ului. Să vedem ce alte lucruri interesante putem face. În acest moment, știm multe. De exemplu, din „
Better together: Java and the Thread class. Part I — Threads of execution ”, știm că clasa Thread reprezintă un fir de execuție. Știm că un fir îndeplinește o anumită sarcină. Dacă vrem ca sarcinile noastre să poată
run
, atunci trebuie să marchem firul cu
Runnable
.
![Mai bine împreună: Java și clasa Thread. Partea a VI-a — Foc departe! - 1]()
Pentru a reține, putem folosi
Tutorialspoint Online Java Compiler :
public static void main(String[] args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
De asemenea, știm că avem ceva numit lacăt. Am aflat despre asta în „
Mai bine împreună: Java și clasa Thread. Partea a II-a — Sincronizarea . Dacă un fir de execuție obține o blocare, atunci un alt fir care încearcă să obțină blocarea va fi forțat să aștepte eliberarea blocării:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
Cred că este timpul să vorbim despre ce alte lucruri interesante putem face.
Semafoare
Cel mai simplu mod de a controla câte fire pot rula simultan este un semafor. Este ca un semnal feroviar. Verde înseamnă să continuăm. Roșu înseamnă așteptare. Așteptați ce de la semafor? Acces. Pentru a avea acces, trebuie să-l obținem. Iar când accesul nu mai este necesar, trebuie să-l dăm sau să-l eliberăm. Să vedem cum funcționează. Trebuie să importăm
java.util.concurrent.Semaphore
clasa. Exemplu:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
După cum puteți vedea, aceste operațiuni (achiziționare și eliberare) ne ajută să înțelegem cum funcționează un semafor. Cel mai important este că dacă vrem să obținem acces, atunci semaforul trebuie să aibă un număr pozitiv de permise. Acest număr poate fi inițializat la un număr negativ. Și putem solicita (dobândi) mai mult de 1 permis.
CountdownLatch
Următorul mecanism este
CountDownLatch
. Deloc surprinzător, acesta este un zăvor cu numărătoare inversă. Aici avem nevoie de instrucțiunea de import adecvată pentru
java.util.concurrent.CountDownLatch
clasă. Este ca o cursă pe jos, în care toată lumea se adună la linia de start. Și odată ce toată lumea este pregătită, toată lumea primește semnalul de pornire în același timp și pornește simultan. Exemplu:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
În primul rând, îi spunem mai întâi zăvorului să
countDown()
. Google definește numărătoarea inversă ca „un act de numărare a cifrelor în ordine inversă până la zero”. Și apoi îi spunem zăvorului să
await()
, adică așteptați până când contorul devine zero. Interesant, acesta este un contor unic. Documentația Java spune: „Când firele de execuție trebuie să facă numărătoare inversă în mod repetat în acest fel, folosiți în schimb un CyclicBarrier”. Cu alte cuvinte, dacă aveți nevoie de un contor reutilizabil, aveți nevoie de o altă opțiune:
CyclicBarrier
.
CyclicBarrier
După cum sugerează și numele,
CyclicBarrier
este o barieră „reutilizabilă”. Va trebui să importăm
java.util.concurrent.CyclicBarrier
clasa. Să ne uităm la un exemplu:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
După cum puteți vedea, firul rulează
await
metoda, adică așteaptă. În acest caz, valoarea barierei scade. Bariera este considerată ruptă (
barrier.isBroken()
) când numărătoarea inversă ajunge la zero. Pentru a reseta bariera, trebuie să apelați
reset()
metoda, care
CountDownLatch
nu are.
Schimbător
Următorul mecanism este Schimbătorul. În acest context, un schimb este un punct de sincronizare în care lucrurile se schimbă sau pot fi schimbate. După cum v-ați aștepta, an
Exchanger
este o clasă care efectuează un schimb sau un schimb. Să ne uităm la cel mai simplu exemplu:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
Aici începem două fire. Fiecare dintre ele rulează metoda de schimb și așteaptă ca celălalt thread să ruleze și metoda de schimb. Procedând astfel, firele schimbă argumentele transmise. Interesant. Nu-ți amintește de ceva? Amintește de
SynchronousQueue
, care se află în centrul lui
CachedThreadPool
. Pentru claritate, iată un exemplu:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
Exemplul arată că atunci când un fir nou este pornit, acesta va aștepta, deoarece coada va fi goală. Și apoi firul principal pune șirul „Mesaj” în coadă. În plus, se va opri și până când acest șir este primit din coadă. Puteți citi, de asemenea, „
SynchronousQueue vs Exchanger ” pentru a afla mai multe despre acest subiect.
Phaser
Am păstrat ce e mai bun pentru final —
Phaser
. Va trebui să importăm
java.util.concurrent.Phaser
clasa. Să ne uităm la un exemplu simplu:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
// By calling the register method, we register the current (main) thread as a party
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
// After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
// We indicate that there will be a +1 party on the Phaser
phaser.register();
// Start a new thread
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
System.out.println(name + " after passing barrier");
}).start();
}
Exemplul ilustrează faptul că atunci când se folosește
Phaser
, bariera se rupe atunci când numărul de înregistrări se potrivește cu numărul de sosiri la barieră. Vă puteți familiariza mai bine
Phaser
citind
acest articol GeeksforGeeks .
rezumat
După cum puteți vedea din aceste exemple, există diferite moduri de a sincroniza firele. Mai devreme, am încercat să-mi amintesc aspecte ale multithreading-ului. Sper că versiunile anterioare din această serie au fost utile. Unii oameni spun că calea către multithreading începe cu cartea „Java Concurrency in Practice”. Deși a fost lansată în 2006, oamenii spun că cartea este destul de fundamentală și actuală și astăzi. De exemplu, puteți citi discuția aici:
Mai este valabilă „Java Concurrency In Practice”? . De asemenea, este util să citiți linkurile din discuție. De exemplu, există un link către cartea
The Well-Grounded Java Developer și vom menționa în mod special
capitolul 4. Concurență modernă . Există, de asemenea, o recenzie întreagă despre acest subiect:
„Java Concurrency in Practice” este încă valabil în epoca Java 8? Acest articol oferă, de asemenea, sugestii despre ce altceva de citit pentru a înțelege cu adevărat acest subiect. După aceea, puteți arunca o privire asupra unei cărți grozave precum
OCA/OCP Java SE 8 Programmer Practice Tests . Ne interesează al doilea acronim: OCP (Oracle Certified Professional). Veți găsi teste în „Capitolul 20: Concurența Java”. Această carte are atât întrebări, cât și răspunsuri cu explicații. De exemplu:
![Mai bine împreună: Java și clasa Thread. Partea a VI-a — Foc departe! - 3]()
Mulți oameni ar putea începe să spună că această întrebare este încă un exemplu de memorare a metodelor. Pe de o parte, da. Pe de altă parte, puteți răspunde la această întrebare amintind că
ExecutorService
este un fel de „upgrade” a
Executor
. Și
Executor
are scopul de a ascunde pur și simplu modul în care sunt create firele de execuție, dar nu este modalitatea principală de a le executa, adică de a începe un
Runnable
obiect pe un fir nou. De aceea nu există
execute(Callable)
- pentru că în
ExecutorService
,
Executor
pur și simplu adaugă
submit()
metode care pot returna un
Future
obiect. Desigur, putem memora o listă de metode, dar este mult mai ușor să ne facem răspunsul pe baza cunoștințelor noastre despre natura claselor în sine. Și iată câteva materiale suplimentare pe această temă:
Mai bine împreună: Java și clasa Thread. Partea I — Fire de execuție Mai bine împreună: Java și clasa Thread. Partea a II-a — Sincronizare Mai bine împreună: Java și clasa Thread. Partea a III-a — Interacțiunea Mai bine împreună: Java și clasa Thread. Partea a IV-a — Apelabil, viitor și prieteni Mai bine împreună: Java și clasa Thread. Partea V — Executor, ThreadPool, Furk/Join
GO TO FULL VERSION