CodeGym /Blog Java /Aleatoriu /Mai bine împreună: Java și clasa Thread. Partea a VI-a — ...
John Squirrels
Nivel
San Francisco

Mai bine împreună: Java și clasa Thread. Partea a VI-a — Foc departe!

Publicat în grup

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!  - 1Pentru 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.Semaphoreclasa. 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.CountDownLatchclasă. 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, CyclicBarriereste o barieră „reutilizabilă”. Va trebui să importăm java.util.concurrent.CyclicBarrierclasa. 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ă awaitmetoda, 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 CountDownLatchnu 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 Exchangereste 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.Phaserclasa. 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 Phasercitind 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!  - 3Mulț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ă ExecutorServiceeste un fel de „upgrade” a Executor. ȘiExecutorare 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 Runnableobiect pe un fir nou. De aceea nu există execute(Callable)- pentru că în ExecutorService, Executorpur și simplu adaugă submit()metode care pot returna un Futureobiect. 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
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION