CodeGym /Java Blog /Willekeurig /Samen beter: Java en de klasse Thread. Deel VI — Vuur weg...
John Squirrels
Niveau 41
San Francisco

Samen beter: Java en de klasse Thread. Deel VI — Vuur weg!

Gepubliceerd in de groep Willekeurig

Invoering

Draden zijn een interessant iets. In eerdere beoordelingen hebben we gekeken naar enkele van de beschikbare tools voor het implementeren van multithreading. Laten we eens kijken welke andere interessante dingen we kunnen doen. Op dit moment weten we veel. Bijvoorbeeld, uit " Samen beter: Java en de klasse Thread. Deel I — Uitvoeringsthreads ", weten we dat de klasse Thread een uitvoeringsthread vertegenwoordigt. We weten dat een thread een bepaalde taak uitvoert. Willen we onze taken kunnen uitvoeren run, dan moeten we de rode draad markeren met Runnable. Samen beter: Java en de klasse Thread.  Deel VI — Vuur weg!  - 1Om te onthouden kunnen we de Tutorialspoint Online Java Compiler gebruiken :

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();
}
We weten ook dat we iets hebben dat een slot wordt genoemd. We leerden hierover in " Samen beter: Java en de Thread-klasse. Deel II - Synchronisatie . Als een thread een vergrendeling krijgt, moet een andere thread die de vergrendeling probeert te verkrijgen, wachten tot de vergrendeling wordt opgeheven:

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();
	}
}
Ik denk dat het tijd is om te praten over andere interessante dingen die we kunnen doen.

Semaforen

De eenvoudigste manier om te bepalen hoeveel threads tegelijkertijd kunnen worden uitgevoerd, is een semafoor. Het is als een spoorwegsignaal. Groen betekent doorgaan. Rood betekent wachten. Wachten op wat van de semafoor? Toegang. Om toegang te krijgen, moeten we het verwerven. En wanneer toegang niet langer nodig is, moeten we het weggeven of vrijgeven. Laten we kijken hoe dit werkt. We moeten de java.util.concurrent.Semaphoreklas importeren. Voorbeeld:

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);
}
Zoals u kunt zien, helpen deze bewerkingen (verkrijgen en vrijgeven) ons te begrijpen hoe een semafoor werkt. Het belangrijkste is dat als we toegang willen krijgen, de semafoor een positief aantal vergunningen moet hebben. Deze telling kan worden geïnitialiseerd op een negatief getal. En we kunnen meer dan 1 vergunning aanvragen (verkrijgen).

CountDownLatch

Het volgende mechanisme is CountDownLatch. Het is niet verwonderlijk dat dit een grendel is met een aftelling. Hier hebben we de juiste importopdracht voor de java.util.concurrent.CountDownLatchklasse nodig. Het is als een hardloopwedstrijd, waarbij iedereen zich verzamelt aan de startlijn. En als iedereen klaar is, krijgt iedereen tegelijk het startsein en start tegelijk. Voorbeeld:

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();
 	}
}
Eerst vertellen we eerst de klink naar countDown(). Google definieert aftellen als "het tellen van getallen in omgekeerde volgorde tot nul". En dan vertellen we de klink om await(), dwz wacht tot de teller nul wordt. Interessant is dat dit een eenmalige teller is. De Java-documentatie zegt: "Als threads herhaaldelijk op deze manier moeten aftellen, gebruik dan een CyclicBarrier". Met andere woorden, als je een herbruikbare teller nodig hebt, heb je een andere optie nodig: CyclicBarrier.

Cyclische barrière

Zoals de naam al aangeeft, CyclicBarrieris het een "herbruikbare" barrière. We zullen de klas moeten importeren java.util.concurrent.CyclicBarrier. Laten we naar een voorbeeld kijken:

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();
	}
}
Zoals je kunt zien, voert de thread de awaitmethode uit, dwz hij wacht. In dit geval neemt de barrièrewaarde af. De barrière wordt als verbroken beschouwd ( barrier.isBroken()) wanneer het aftellen nul bereikt. reset()Om de barrière te resetten, moet u de methode aanroepen , die CountDownLatchniet heeft.

Uitwisselaar

Het volgende mechanisme is Exchanger. Een Exchange is in deze context een synchronisatiepunt waar dingen worden uitgewisseld of geruild. Zoals je zou verwachten, Exchangeris an een klasse die een uitwisseling of ruil uitvoert. Laten we naar het eenvoudigste voorbeeld kijken:

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();
}
Hier beginnen we twee threads. Elk van hen voert de uitwisselingsmethode uit en wacht tot de andere thread ook de uitwisselingsmethode uitvoert. Daarbij wisselen de threads de doorgegeven argumenten uit. Interessant. Doet het je niet aan iets denken? Het doet denken aan SynchronousQueue, wat de kern vormt van CachedThreadPool. Voor de duidelijkheid, hier is een voorbeeld:

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");
}
Het voorbeeld laat zien dat wanneer een nieuwe thread wordt gestart, deze zal wachten, omdat de wachtrij dan leeg is. En dan plaatst de hoofdthread de string "Bericht" in de wachtrij. Bovendien stopt het ook totdat deze string uit de wachtrij wordt ontvangen. U kunt ook " SynchronousQueue vs Exchanger " lezen om meer over dit onderwerp te vinden.

Phaser

We hebben het beste voor het laatst bewaard — Phaser. We zullen de klas moeten importeren java.util.concurrent.Phaser. Laten we een eenvoudig voorbeeld bekijken:

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();
    }
Het voorbeeld illustreert dat bij gebruik van Phaserde slagboom doorbreekt als het aantal aanmeldingen overeenkomt met het aantal aankomsten bij de slagboom. U kunt er meer vertrouwd mee raken Phaserdoor dit GeeksforGeeks-artikel te lezen .

Samenvatting

Zoals u in deze voorbeelden kunt zien, zijn er verschillende manieren om threads te synchroniseren. Eerder probeerde ik me aspecten van multithreading te herinneren. Ik hoop dat de vorige afleveringen in deze serie nuttig waren. Sommige mensen zeggen dat de weg naar multithreading begint met het boek "Java Concurrency in Practice". Hoewel het in 2006 werd uitgebracht, zeggen mensen dat het boek vrij fundamenteel is en nog steeds relevant is. U kunt de discussie bijvoorbeeld hier lezen: Is "Java Concurrency In Practice" nog steeds geldig? . Het is ook handig om de links in de discussie te lezen. Er is bijvoorbeeld een link naar het boek The Well-Grounded Java Developer , en we zullen in het bijzonder hoofdstuk 4. Modern concurrency noemen . Er is ook een hele recensie over dit onderwerp:Is "Java-concurrency in de praktijk" nog steeds geldig in het tijdperk van Java 8? Dat artikel biedt ook suggesties over wat u nog meer kunt lezen om dit onderwerp echt te begrijpen. Daarna zou je een geweldig boek als OCA/OCP Java SE 8 Programmer Practice Tests kunnen bekijken . We zijn geïnteresseerd in het tweede acroniem: OCP (Oracle Certified Professional). Tests vindt u in "Hoofdstuk 20: Java Concurrency". Dit boek bevat zowel vragen als antwoorden met uitleg. Bijvoorbeeld: Samen beter: Java en de klasse Thread.  Deel VI — Vuur weg!  - 3Veel mensen zouden kunnen beginnen te zeggen dat deze vraag het zoveelste voorbeeld is van het onthouden van methoden. Aan de ene kant wel. Aan de andere kant zou je deze vraag kunnen beantwoorden door eraan te herinneren dat dit ExecutorServiceeen soort "upgrade" is van Executor. EnExecutorRunnableis bedoeld om eenvoudigweg de manier te verbergen waarop threads worden gemaakt, maar het is niet de belangrijkste manier om ze uit te voeren, dat wil zeggen een object starten op een nieuwe thread. Daarom is er geen execute(Callable)— omdat in ExecutorService, het Executorvoegt eenvoudigweg submit()methoden toe die een Futureobject kunnen retourneren. Natuurlijk kunnen we een lijst met methoden onthouden, maar het is veel gemakkelijker om ons antwoord te geven op basis van onze kennis van de aard van de klassen zelf. En hier zijn enkele aanvullende materialen over het onderwerp: Samen beter: Java en de klasse Thread. Deel I — Uitvoeringsthreads Beter samen: Java en de klasse Thread. Deel II — Synchronisatie Samen beter: Java en de klasse Thread. Deel III — Interactie Samen beter: Java en de klasse Thread. Deel IV — Callable, Future en vrienden Samen beter: Java en de Thread-klasse. Deel V — Uitvoerder, ThreadPool, Fork/Join
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION