CodeGym /Java blog /Véletlen /Jobb együtt: Java és a Thread osztály. VI. rész – Tüzet e...
John Squirrels
Szint
San Francisco

Jobb együtt: Java és a Thread osztály. VI. rész – Tüzet el!

Megjelent a csoportban

Bevezetés

Érdekes dolog a szál. A korábbi áttekintésekben megvizsgáltunk néhány elérhető eszközt a többszálú feldolgozás megvalósításához. Lássuk, milyen érdekes dolgokat tehetünk még. Ezen a ponton sok mindent tudunk. Például a " Jobb együtt: Java és a szál osztály. I. rész – Végrehajtási szálak " alapján tudjuk, hogy a Thread osztály egy végrehajtási szálat képvisel. Tudjuk, hogy egy szál végrehajt valamilyen feladatot. Ha azt akarjuk, hogy a feladataink képesek legyenek run, akkor a szálat a -val kell megjelölnünk Runnable. Jobb együtt: Java és a Thread osztály.  VI. rész – Tüzet el!  - 1Emlékeztetni kell arra, hogy használhatjuk a Tutorialspoint Online Java fordítót :

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();
}
Azt is tudjuk, hogy van valami, amit zárnak hívnak. Erről a " Jobb együtt: Java és a szál osztály. II. rész — Szinkronizálás című részből tanultunk . Ha az egyik szál zárolást szerez, akkor egy másik szál, amely megpróbálja megszerezni a zárolást, kénytelen megvárni a zárolás feloldását:

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();
	}
}
Azt hiszem, itt az ideje, hogy beszéljünk arról, milyen érdekes dolgokat tehetünk még.

Szemaforok

A legegyszerűbb módja annak, hogy szabályozza, hogy hány szál futhasson egyszerre, egy szemafor. Olyan, mint egy vasúti jelzés. A zöld azt jelenti, hogy tovább. A piros azt jelenti, hogy várni kell. Mit várunk a szemafortól? Hozzáférés. A hozzáféréshez meg kell szereznünk. És ha már nincs szükség a hozzáférésre, akkor el kell adnunk vagy el kell engednünk. Lássuk, hogyan működik ez. Importálnunk kell az java.util.concurrent.Semaphoreosztályt. Példa:

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);
}
Amint látja, ezek a műveletek (megszerzés és elengedés) segítenek megérteni a szemafor működését. A legfontosabb az, hogy ha hozzá akarunk jutni, akkor a szemafornak pozitív engedélyszámmal kell rendelkeznie. Ez a szám negatív számra inicializálható. És 1-nél több engedélyt is kérhetünk (beszerezhetünk).

CountDownLatch

A következő mechanizmus az CountDownLatch. Nem meglepő módon ez egy visszaszámlálós retesz. Itt szükségünk van az osztály megfelelő import utasítására java.util.concurrent.CountDownLatch. Olyan ez, mint egy lábverseny, ahol mindenki a rajtvonalnál gyűlik össze. És ha mindenki készen áll, mindenki egyszerre kapja meg az indítójelet és egyszerre indul. Példa:

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();
 	}
}
Először is először közöljük a reteszt, hogy countDown(). A Google a visszaszámlálást úgy definiálja, mint "a számok számlálása fordított sorrendben nullára". És akkor azt mondjuk a retesznek, hogy await(), azaz várja meg, amíg a számláló nullává válik. Érdekes módon ez egy egyszeri számláló. A Java dokumentáció azt mondja: "Ha a szálakat ismételten vissza kell számolni ilyen módon, használjon CyclicBarriert". Más szóval, ha újrafelhasználható számlálóra van szüksége, akkor egy másik lehetőségre van szüksége: CyclicBarrier.

CyclicBarrier

Ahogy a neve is sugallja, CyclicBarrierez egy "újrafelhasználható" akadály. Importálnunk kell az java.util.concurrent.CyclicBarrierosztályt. Nézzünk egy példát:

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();
	}
}
Amint látható, a szál futtatja a awaitmetódust, azaz vár. Ebben az esetben a gát értéke csökken. A sorompó akkor tekinthető megszakadtnak ( barrier.isBroken()), ha a visszaszámlálás eléri a nullát. Az akadály visszaállításához meg kell hívnia a reset()módszert, amely CountDownLatchnem rendelkezik.

Hőcserélő

A következő mechanizmus az Exchanger. Ebben az összefüggésben az Exchange egy olyan szinkronizálási pont, ahol a dolgok megváltoznak, vagy felcserélhetők. Ahogy az várható volt, az an Exchangeregy olyan osztály, amely cserét vagy cserét hajt végre. Nézzük a legegyszerűbb példát:

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();
}
Itt két szálat indítunk. Mindegyikük futtatja a cseremódszert, és megvárja, hogy a másik szál is futtatja a cseremódszert. Ennek során a szálak kicserélik az átadott argumentumokat. Érdekes. Nem emlékeztet valamire? Arra emlékeztet SynchronousQueue, ami a középpontjában található CachedThreadPool. Az érthetőség kedvéért álljon itt egy példa:

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");
}
A példa azt mutatja, hogy amikor egy új szál indul, az vár, mert a sor üres lesz. Ezután a fő szál behelyezi az "Üzenet" karakterláncot a sorba. Sőt, addig is leáll, amíg ez a karakterlánc meg nem érkezik a sorból. Olvassa el a " SynchronousQueue vs Exchanger " című részt is, hogy többet megtudjon erről a témáról.

Phaser

A legjobbat a végére tartogattuk – Phaser. Importálnunk kell az java.util.concurrent.Phaserosztályt. Nézzünk egy egyszerű példát:

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();
    }
A példa azt szemlélteti, hogy a használatakor Phasera sorompó letörik, ha a regisztrációk száma megegyezik a sorompóhoz érkezők számával. A GeeksforGeeks cikkénekPhaser elolvasásával jobban megismerheti .

Összegzés

Amint az ezekből a példákból látható, a szálak szinkronizálásának többféle módja van. Korábban megpróbáltam felidézni a többszálú feldolgozás szempontjait. Remélem, a sorozat korábbi részei hasznosak voltak. Vannak, akik azt mondják, hogy a többszálú használathoz vezető út a „Java Concurrency in Practice” című könyvvel kezdődik. Bár 2006-ban adták ki, az emberek azt mondják, hogy a könyv meglehetősen alapvető és még ma is aktuális. Például itt olvashatja a vitát: Érvényes még a „Java Concurrency In Practice”? . Hasznos elolvasni a beszélgetésben található linkeket is. Például van egy link a The Well-Grounded Java Developer című könyvre , és külön említést teszünk a 4. Modern párhuzamosság című fejezetről . Erről a témáról van egy teljes ismertető is:A „Java párhuzamosság a gyakorlatban” még mindig érvényes a Java 8 korszakában? Ez a cikk javaslatokat is kínál arra vonatkozóan, hogy mit érdemes még elolvasni a téma valódi megértéséhez. Ezek után belenézhetsz egy nagyszerű könyvbe, például az OCA/OCP Java SE 8 programozói gyakorlati tesztekbe . A második mozaikszó érdekel bennünket: OCP (Oracle Certified Professional). A teszteket a „20. fejezet: Java párhuzamosság” részben találja. Ez a könyv kérdéseket és válaszokat tartalmaz magyarázatokkal együtt. Például: Jobb együtt: Java és a Thread osztály.  VI. rész – Tüzet el!  - 3Sokan azt mondják, hogy ez a kérdés egy újabb példa a módszerek memorizálására. Egyrészt igen. Másrészt megválaszolhatja ezt a kérdést, ha felidézi, hogy ez ExecutorServiceegyfajta "frissítés" a Executor. ÉsExecutorcélja, hogy egyszerűen elrejtse a szálak létrehozásának módját, de nem ez a fő módja a végrehajtásuknak, vagyis egy Runnableobjektum elindítása egy új szálon. Ez az oka annak, hogy nincs execute(Callable)– mert ExecutorServicea -ban Executoregyszerűen hozzáadja submit()a metódusokat, amelyek visszaadhatnak egy Futureobjektumot. Természetesen megjegyezhetünk egy listát a módszerekről, de sokkal egyszerűbb az órák természetére vonatkozó ismereteink alapján megválaszolni a választ. És itt van néhány további anyag a témában: Jobb együtt: Java és a Thread osztály. I. rész – A végrehajtás szálai Jobb együtt: Java és a Thread osztály. II. rész – Szinkronizálás Jobb együtt: Java és a Thread osztály. III. rész – Interakció Jobb együtt: Java és a szál osztály. IV. rész – Hívható, jövő és barátok Jobb együtt: Java és a szál osztály. V. rész – Végrehajtó, ThreadPool, Fork/Join
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION