CodeGym /Blog Java /Random-PL /Razem lepiej: Java i klasa Thread. Część VI — Odpal!
John Squirrels
Poziom 41
San Francisco

Razem lepiej: Java i klasa Thread. Część VI — Odpal!

Opublikowano w grupie Random-PL

Wstęp

Wątki to ciekawa rzecz. W poprzednich recenzjach przyjrzeliśmy się niektórym dostępnym narzędziom do implementacji wielowątkowości. Zobaczmy, jakie inne ciekawe rzeczy możemy zrobić. W tym momencie wiemy bardzo dużo. Na przykład z „ Lepiej razem: Java i klasa Thread. Część I — Wątki wykonania ” wiemy, że klasa Thread reprezentuje wątek wykonania. Wiemy, że wątek wykonuje jakieś zadanie. Jeśli chcemy, aby nasze zadania mogły to robić run, musimy oznaczyć wątek za pomocą Runnable. Razem lepiej: Java i klasa Thread.  Część VI — Odpal!  - 1Dla przypomnienia możemy skorzystać z kompilatora Java Tutorialspoint Online :

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();
}
Wiemy też, że mamy coś, co nazywamy zamkiem. Dowiedzieliśmy się o tym w „ Lepiej razem: Java i klasa Thread. Część II — Synchronizacja . Jeśli jeden wątek uzyska blokadę, to inny wątek próbujący uzyskać blokadę będzie zmuszony czekać na zwolnienie blokady:

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();
	}
}
Myślę, że nadszedł czas, aby porozmawiać o tym, jakie inne ciekawe rzeczy możemy robić.

Semafory

Najprostszym sposobem kontrolowania, ile wątków może działać jednocześnie, jest semafor. To jak sygnał kolejowy. Zielony oznacza kontynuację. Czerwony oznacza czekać. Czekaj na co z semafora? Dostęp. Aby uzyskać dostęp, musimy go zdobyć. A kiedy dostęp nie jest już potrzebny, musimy go oddać lub zwolnić. Zobaczmy, jak to działa. Musimy zaimportować java.util.concurrent.Semaphoreklasę. Przykład:

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);
}
Jak widać, te operacje (acquire i release) pomagają nam zrozumieć, jak działa semafor. Najważniejsze jest to, że jeśli mamy uzyskać dostęp, to semafor musi mieć dodatnią liczbę zezwoleń. Ta liczba może być zainicjowana liczbą ujemną. I możemy poprosić (zdobyć) więcej niż 1 pozwolenie.

Odliczanie Zatrzask

Kolejnym mechanizmem jest CountDownLatch. Nic dziwnego, że jest to zatrzask z odliczaniem. Tutaj potrzebujemy odpowiedniej instrukcji importu dla java.util.concurrent.CountDownLatchklasy. To jak wyścig pieszy, w którym wszyscy zbierają się na linii startu. A kiedy wszyscy są gotowi, wszyscy otrzymują sygnał startu w tym samym czasie i jednocześnie startują. Przykład:

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();
 	}
}
Najpierw mówimy zatrzaskowi, aby countDown(). Google definiuje odliczanie jako „akt liczenia cyfr w odwrotnej kolejności do zera”. Następnie mówimy zatrzaskowi, aby await(), czyli czekał, aż licznik osiągnie zero. Co ciekawe, jest to licznik jednorazowy. Dokumentacja Java mówi: „Gdy wątki muszą wielokrotnie odliczać w ten sposób, zamiast tego użyj CyclicBarrier”. Innymi słowy, jeśli potrzebujesz licznika wielokrotnego użytku, potrzebujesz innej opcji: CyclicBarrier.

Cykliczna Bariera

Jak sama nazwa wskazuje, CyclicBarrierjest to bariera „wielokrotnego użytku”. Będziemy musieli zaimportować java.util.concurrent.CyclicBarrierklasę. Spójrzmy na przykład:

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();
	}
}
Jak widać, wątek uruchamia awaitmetodę, czyli czeka. W tym przypadku wartość bariery maleje. Bariera jest uważana za przełamaną ( barrier.isBroken()), gdy odliczanie osiągnie zero. Aby zresetować barierę, musisz wywołać reset()metodę, której CountDownLatchnie ma.

Wymiennik

Kolejnym mechanizmem jest Exchanger. W tym kontekście Giełda jest punktem synchronizacji, w którym rzeczy są wymieniane lub wymieniane. Jak można się spodziewać, an Exchangerjest klasą, która wykonuje wymianę lub zamianę. Spójrzmy na najprostszy przykład:

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();
}
Tutaj zaczynamy dwa wątki. Każdy z nich uruchamia metodę wymiany i czeka, aż drugi wątek również uruchomi metodę wymiany. W ten sposób wątki wymieniają przekazane argumenty. Ciekawy. Nie przypomina ci to czegoś? Przypomina SynchronousQueue, który leży u podstaw CachedThreadPool. Dla jasności oto przykład:

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");
}
Przykład pokazuje, że po uruchomieniu nowego wątku będzie on czekał, ponieważ kolejka będzie pusta. A następnie główny wątek umieszcza ciąg „Wiadomość” w kolejce. Co więcej, zatrzyma się również, dopóki ten ciąg nie zostanie odebrany z kolejki. Możesz także przeczytać „ SynchronousQueue vs Exchanger ”, aby dowiedzieć się więcej na ten temat.

Fazer

Najlepsze zostawiliśmy na koniec — Phaser. Będziemy musieli zaimportować java.util.concurrent.Phaserklasę. Spójrzmy na prosty przykład:

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();
    }
Przykład ilustruje, że przy użyciu Phaser, bariera pęka, gdy liczba rejestracji odpowiada liczbie przybyć do bariery. Możesz zapoznać się z tym Phaser, czytając ten artykuł GeeksforGeeks .

Streszczenie

Jak widać z tych przykładów, istnieją różne sposoby synchronizacji wątków. Wcześniej próbowałem przypomnieć sobie aspekty wielowątkowości. Mam nadzieję, że poprzednie części z tej serii były przydatne. Niektórzy twierdzą, że droga do wielowątkowości zaczyna się od książki „Java Concurrency in Practice”. Chociaż została wydana w 2006 roku, ludzie mówią, że jest dość fundamentalna i nadal aktualna. Na przykład możesz przeczytać dyskusję tutaj: Czy „Java Concurrency In Practice” jest nadal aktualna? . Przydatne jest również czytanie linków w dyskusji. Na przykład istnieje łącze do książki The Well-Grounded Java Developer , aw szczególności wspomnimy o rozdziale 4. Nowoczesna współbieżność . Jest też cała recenzja na ten temat:Czy „współbieżność Java w praktyce” jest nadal aktualna w erze Java 8? Ten artykuł zawiera również sugestie dotyczące tego, co jeszcze warto przeczytać, aby naprawdę zrozumieć ten temat. Następnie możesz zajrzeć do świetnej książki, takiej jak OCA/OCP Java SE 8 Programmer Practice Tests . Nas interesuje drugi akronim: OCP (Oracle Certified Professional). Testy znajdziesz w „Rozdziale 20: Współbieżność Java”. Ta książka zawiera zarówno pytania, jak i odpowiedzi z wyjaśnieniami. Na przykład: Razem lepiej: Java i klasa Thread.  Część VI — Odpal!  - 3Wiele osób może zacząć mówić, że to pytanie jest kolejnym przykładem zapamiętywania metod. Z jednej strony tak. Z drugiej strony możesz odpowiedzieć na to pytanie, przypominając sobie, że ExecutorServicejest to swego rodzaju „uaktualnienie” Executor. IExecutorma na celu po prostu ukryć sposób tworzenia wątków, ale nie jest to główny sposób ich wykonywania, czyli uruchamiania obiektu Runnablew nowym wątku. Dlatego nie ma execute(Callable)— ponieważ w ExecutorService, Executorpo prostu dodaje submit()metody, które mogą zwrócić Futureobiekt. Oczywiście możemy zapamiętać listę metod, ale o wiele łatwiej jest udzielić odpowiedzi w oparciu o naszą wiedzę o naturze samych klas. A oto kilka dodatkowych materiałów na ten temat: Razem lepiej: Java i klasa Thread. Część I — Wątki wykonania Lepiej razem: Java i klasa Thread. Część II — Synchronizacja Lepiej razem: Java i klasa Thread. Część III — Interakcja Lepiej razem: Java i klasa Thread. Część IV — Callable, Future i friends Razem lepiej: Java i klasa Thread. Część V — Executor, ThreadPool, Fork/Join
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION