Einführung

Threads sind eine interessante Sache. In früheren Rezensionen haben wir uns einige der verfügbaren Tools zur Implementierung von Multithreading angesehen. Mal sehen, welche anderen interessanten Dinge wir tun können. Zu diesem Zeitpunkt wissen wir viel. Aus „ Besser zusammen: Java und die Thread-Klasse. Teil I – Ausführungsthreads “ wissen wir beispielsweise , dass die Thread-Klasse einen Ausführungsthread darstellt. Wir wissen, dass ein Thread eine Aufgabe ausführt. Wenn wir möchten, dass unsere Aufgaben dies können run, müssen wir den Thread mit markieren Runnable. Zur Erinnerung können wir den Tutorialspoint Online Java CompilerBesser zusammen: Java und die Thread-Klasse.  Teil VI – Feuer los!  - 1 verwenden :

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();
}
Wir wissen auch, dass wir ein sogenanntes Schloss haben. Wir haben darüber in „ Besser zusammen: Java und die Thread-Klasse. Teil II – Synchronisierung“ erfahren. Wenn ein Thread eine Sperre erwirbt, muss ein anderer Thread, der versucht, die Sperre zu erlangen, auf die Freigabe der Sperre warten:

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();
	}
}
Ich denke, es ist an der Zeit, darüber zu sprechen, welche anderen interessanten Dinge wir tun können.

Semaphore

Der einfachste Weg, um zu steuern, wie viele Threads gleichzeitig ausgeführt werden können, ist ein Semaphor. Es ist wie ein Eisenbahnsignal. Grün bedeutet fortfahren. Rot bedeutet warten. Was erwartet Sie vom Semaphor? Zugang. Um Zugang zu erhalten, müssen wir es erwerben. Und wenn der Zugriff nicht mehr benötigt wird, müssen wir ihn verschenken oder freigeben. Mal sehen, wie das funktioniert. Wir müssen die java.util.concurrent.SemaphoreKlasse importieren. Beispiel:

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);
}
Wie Sie sehen, helfen uns diese Vorgänge (Erfassen und Freigeben) zu verstehen, wie ein Semaphor funktioniert. Das Wichtigste ist, dass der Semaphor eine positive Anzahl an Genehmigungen haben muss, um Zugang zu erhalten. Dieser Zähler kann auf eine negative Zahl initialisiert werden. Und wir können mehr als eine Genehmigung beantragen (erwerben).

CountDownLatch

Der nächste Mechanismus ist CountDownLatch. Es überrascht nicht, dass es sich hierbei um einen Riegel mit Countdown handelt. Hier benötigen wir die entsprechende Importanweisung für die java.util.concurrent.CountDownLatchKlasse. Es ist wie ein Wettlauf, bei dem sich alle an der Startlinie versammeln. Und wenn alle bereit sind, erhalten alle gleichzeitig den Startschuss und starten gleichzeitig. Beispiel:

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();
 	}
}
Zuerst weisen wir den Riegel an countDown(). Google definiert Countdown als „einen Vorgang, bei dem Zahlen in umgekehrter Reihenfolge bis Null gezählt werden“. Und dann sagen wir dem Latch await(), dass er warten soll, bis der Zähler Null wird. Interessanterweise handelt es sich hierbei um einen einmaligen Zähler. In der Java-Dokumentation heißt es: „Wenn Threads wiederholt auf diese Weise heruntergezählt werden müssen, verwenden Sie stattdessen eine CyclicBarrier.“ Mit anderen Worten: Wenn Sie einen wiederverwendbaren Zähler benötigen, benötigen Sie eine andere Option: CyclicBarrier.

CyclicBarrier

Wie der Name schon sagt, CyclicBarrierhandelt es sich um eine „wiederverwendbare“ Barriere. Wir müssen die java.util.concurrent.CyclicBarrierKlasse importieren. Schauen wir uns ein Beispiel an:

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();
	}
}
Wie Sie sehen, führt der Thread die awaitMethode aus, dh er wartet. In diesem Fall sinkt der Barrierewert. Die Barriere gilt als durchbrochen ( barrier.isBroken()), wenn der Countdown Null erreicht. reset()Um die Barriere zurückzusetzen, müssen Sie die Methode aufrufen , die CountDownLatchnicht vorhanden ist.

Austauscher

Der nächste Mechanismus ist Exchanger. In diesem Zusammenhang ist ein Exchange ein Synchronisationspunkt, an dem Dinge ausgetauscht oder getauscht werden. Wie zu erwarten ist, Exchangerhandelt es sich bei an um eine Klasse, die einen Austausch durchführt. Schauen wir uns das einfachste Beispiel an:

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 starten wir zwei Threads. Jeder von ihnen führt die Exchange-Methode aus und wartet darauf, dass der andere Thread ebenfalls die Exchange-Methode ausführt. Dabei tauschen die Threads die übergebenen Argumente aus. Interessant. Erinnert es Sie nicht an etwas? Es erinnert an SynchronousQueue, was das Herzstück von ist CachedThreadPool. Zur Verdeutlichung hier ein Beispiel:

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");
}
Das Beispiel zeigt, dass beim Start eines neuen Threads dieser wartet, da die Warteschlange leer ist. Und dann stellt der Hauptthread die Zeichenfolge „Nachricht“ in die Warteschlange. Darüber hinaus wird es auch angehalten, bis diese Zeichenfolge aus der Warteschlange empfangen wird. Sie können auch „ SynchronousQueue vs Exchanger “ lesen, um mehr über dieses Thema zu erfahren.

Phaser

Wir haben das Beste zum Schluss aufgehoben – Phaser. Wir müssen die java.util.concurrent.PhaserKlasse importieren. Schauen wir uns ein einfaches Beispiel an:

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();
    }
Das Beispiel zeigt, dass bei Verwendung Phaservon die Schranke bricht, wenn die Anzahl der Registrierungen mit der Anzahl der Ankünfte an der Schranke übereinstimmt. Sie können sich besser damit vertraut machen , indem Sie diesen Artikel von GeeksforGeeksPhaser lesen .

Zusammenfassung

Wie Sie anhand dieser Beispiele sehen können, gibt es verschiedene Möglichkeiten, Threads zu synchronisieren. Zuvor habe ich versucht, mich an Aspekte des Multithreadings zu erinnern. Ich hoffe, dass die vorherigen Teile dieser Serie hilfreich waren. Manche Leute sagen, dass der Weg zum Multithreading mit dem Buch „Java Concurrency in Practice“ beginnt. Obwohl es bereits 2006 veröffentlicht wurde, sagen die Leute, dass das Buch ziemlich grundlegend und auch heute noch relevant ist. Die Diskussion können Sie beispielsweise hier nachlesen: Ist „Java Concurrency In Practice“ noch gültig? . Es ist auch nützlich, die Links in der Diskussion zu lesen. Es gibt beispielsweise einen Link zum Buch The Well-Grounded Java Developer , und wir werden insbesondere Kapitel 4 erwähnen. Moderne Parallelität . Zu diesem Thema gibt es auch eine komplette Rezension:Ist „Java-Parallelität in der Praxis“ im Zeitalter von Java 8 noch gültig? Dieser Artikel bietet auch Vorschläge, was Sie sonst noch lesen sollten, um dieses Thema wirklich zu verstehen. Danach könnten Sie einen Blick auf ein großartiges Buch wie „ OCA/OCP Java SE 8 Programmer Practice Tests“ werfen . Uns interessiert das zweite Akronym: OCP (Oracle Certified Professional). Tests finden Sie in „Kapitel 20: Java-Parallelität“. Dieses Buch enthält sowohl Fragen als auch Antworten mit Erklärungen. Zum Beispiel: Besser zusammen: Java und die Thread-Klasse.  Teil VI – Feuer los!  - 3Viele Leute sagen vielleicht, dass diese Frage ein weiteres Beispiel für das Auswendiglernen von Methoden ist. Einerseits ja. Andererseits könnten Sie diese Frage beantworten, indem Sie sich daran erinnern, dass es sich um ExecutorServiceeine Art „Upgrade“ von handelt Executor. UndExecutorsoll lediglich die Art und Weise verbergen, wie Threads erstellt werden, ist jedoch nicht die Hauptmethode für deren Ausführung, d. h. das Starten eines RunnableObjekts in einem neuen Thread. Deshalb gibt es keine execute(Callable)– denn in fügt ExecutorServicedie Executoreinfach submit()Methoden hinzu, die ein Objekt zurückgeben können Future. Natürlich können wir uns eine Liste von Methoden merken, aber es ist viel einfacher, unsere Antwort auf der Grundlage unseres Wissens über die Natur der Klassen selbst zu formulieren. Und hier sind einige zusätzliche Materialien zum Thema: Besser zusammen: Java und die Thread-Klasse. Teil I – Ausführungsthreads Gemeinsam besser: Java und die Thread-Klasse. Teil II – Synchronisierung Gemeinsam besser: Java und die Thread-Klasse. Teil III – Gemeinsam besser interagieren: Java und die Thread-Klasse. Teil IV – Callable, Future und Freunde Gemeinsam besser: Java und die Thread-Klasse. Teil V – Executor, ThreadPool, Fork/Join