CodeGym /Blog Java /rawak /Lebih baik bersama: Java dan kelas Thread. Bahagian VI - ...
John Squirrels
Tahap
San Francisco

Lebih baik bersama: Java dan kelas Thread. Bahagian VI - Jauhkan api!

Diterbitkan dalam kumpulan

pengenalan

Benang adalah perkara yang menarik. Dalam ulasan lepas, kami melihat beberapa alat yang tersedia untuk melaksanakan multithreading. Mari lihat apa lagi perkara menarik yang boleh kita lakukan. Pada ketika ini, kita tahu banyak. Contohnya, daripada " Better together: Java dan kelas Thread. Bahagian I — Threads of execution ", kita tahu bahawa kelas Thread mewakili thread of execution. Kami tahu bahawa benang melaksanakan beberapa tugas. Jika kita mahu tugas kita dapat run, maka kita mesti menandakan benang dengan Runnable. Lebih baik bersama: Java dan kelas Thread.  Bahagian VI - Jauhkan api!  - 1Untuk diingati, kita boleh menggunakan 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();
}
Kami juga tahu bahawa kami mempunyai sesuatu yang dipanggil kunci. Kami belajar tentang perkara ini dalam " Lebih baik bersama-sama: Java dan kelas Benang. Bahagian II — Penyegerakan . Jika satu utas memperoleh kunci, maka utas lain yang cuba memperoleh kunci akan terpaksa menunggu kunci dilepaskan:

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();
	}
}
Saya rasa sudah tiba masanya untuk bercakap tentang perkara menarik lain yang boleh kita lakukan.

Semaphore

Cara paling mudah untuk mengawal bilangan utas yang boleh dijalankan secara serentak ialah semafor. Ia seperti isyarat kereta api. Hijau bermakna teruskan. Merah bermakna tunggu. Tunggu apa dari semaphore? Akses. Untuk mendapatkan akses, kita mesti memperolehnya. Dan apabila akses tidak lagi diperlukan, kita mesti memberikannya atau melepaskannya. Mari lihat bagaimana ini berfungsi. Kita perlu mengimport java.util.concurrent.Semaphorekelas. Contoh:

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);
}
Seperti yang anda lihat, operasi ini (memperoleh dan melepaskan) membantu kami memahami cara semaphore berfungsi. Perkara yang paling penting ialah jika kita ingin mendapatkan akses, maka semaphore mesti mempunyai bilangan permit yang positif. Kiraan ini boleh dimulakan kepada nombor negatif. Dan kami boleh meminta (memperoleh) lebih daripada 1 permit.

CountDownLatch

Mekanisme seterusnya ialah CountDownLatch. Tidak mengejutkan, ini adalah selak dengan kira detik. Di sini kita memerlukan pernyataan import yang sesuai untuk java.util.concurrent.CountDownLatchkelas. Ia seperti perlumbaan kaki, di mana semua orang berkumpul di garisan permulaan. Dan setelah semua orang bersedia, semua orang menerima isyarat permulaan pada masa yang sama dan bermula serentak. Contoh:

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();
 	}
}
Mula-mula, kami mula-mula memberitahu selak kepada countDown(). Google mentakrifkan undur sebagai "tindakan mengira angka dalam susunan terbalik kepada sifar". Dan kemudian kami memberitahu selak kepada await(), iaitu tunggu sehingga kaunter menjadi sifar. Menariknya, ini adalah kaunter sekali sahaja. Dokumentasi Java mengatakan, "Apabila benang mesti berulang kali mengira mundur dengan cara ini, sebaliknya gunakan CyclicBarrier". Dengan kata lain, jika anda memerlukan kaunter boleh guna semula, anda memerlukan pilihan yang berbeza: CyclicBarrier.

Penghalang Kitaran

Seperti namanya, CyclicBarrieradalah penghalang "boleh digunakan semula". Kami perlu mengimport java.util.concurrent.CyclicBarrierkelas. Mari lihat contoh:

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();
	}
}
Seperti yang anda lihat, benang menjalankan awaitkaedah, iaitu ia menunggu. Dalam kes ini, nilai halangan berkurangan. Penghalang dianggap pecah ( barrier.isBroken()) apabila kira detik mencapai sifar. Untuk menetapkan semula halangan, anda perlu memanggil reset()kaedah, yang CountDownLatchtidak mempunyai.

Penukar

Mekanisme seterusnya ialah Exchanger. Dalam konteks ini, Exchange ialah titik penyegerakan di mana perkara berubah ditukar atau ditukar. Seperti yang anda jangkakan, an Exchangerialah kelas yang melakukan pertukaran atau pertukaran. Mari lihat contoh paling mudah:

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();
}
Di sini kita mulakan dua utas. Setiap daripada mereka menjalankan kaedah pertukaran dan menunggu utas lain juga menjalankan kaedah pertukaran. Dengan berbuat demikian, utas bertukar-tukar hujah yang diluluskan. Menarik. Tidakkah ia mengingatkan anda tentang sesuatu? Ia mengingatkan SynchronousQueue, yang terletak di tengah-tengah CachedThreadPool. Untuk kejelasan, berikut ialah contoh:

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");
}
Contoh menunjukkan bahawa apabila benang baharu dimulakan, ia akan menunggu, kerana baris gilir akan kosong. Dan kemudian benang utama meletakkan rentetan "Mesej" ke dalam baris gilir. Apatah lagi, ia juga akan berhenti sehingga rentetan ini diterima daripada baris gilir. Anda juga boleh membaca " SynchronousQueue vs Exchanger " untuk mengetahui lebih lanjut mengenai topik ini.

Phaser

Kami telah menyimpan yang terbaik untuk yang terakhir — Phaser. Kami perlu mengimport java.util.concurrent.Phaserkelas. Mari lihat contoh mudah:

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();
    }
Contoh menggambarkan bahawa apabila menggunakan Phaser, halangan pecah apabila bilangan pendaftaran sepadan dengan bilangan ketibaan di penghalang. Anda boleh membiasakan diri Phaserdengan membaca artikel GeeksforGeeks ini .

Ringkasan

Seperti yang anda boleh lihat daripada contoh ini, terdapat pelbagai cara untuk menyegerakkan benang. Terdahulu, saya cuba mengingat kembali aspek multithreading. Saya harap ansuran sebelumnya dalam siri ini berguna. Sesetengah orang mengatakan bahawa laluan ke multithreading bermula dengan buku "Java Concurrency in Practice". Walaupun ia dikeluarkan pada tahun 2006, orang mengatakan bahawa buku itu agak asas dan masih relevan hari ini. Sebagai contoh, anda boleh membaca perbincangan di sini: Adakah "Java Concurrency Dalam Amalan" masih sah? . Ia juga berguna untuk membaca pautan dalam perbincangan. Sebagai contoh, terdapat pautan kepada buku The Well-Grounded Java Developer , dan kami akan menyebut secara khusus Bab 4. Modern concurrency . Terdapat juga ulasan keseluruhan tentang topik ini:Adakah "Java Concurrency dalam Amalan" Masih Sah dalam Era Java 8? Artikel itu juga menawarkan cadangan tentang perkara lain yang perlu dibaca untuk benar-benar memahami topik ini. Selepas itu, anda boleh melihat buku yang hebat seperti Ujian Amalan Pengaturcara OCA/OCP Java SE 8 . Kami berminat dengan akronim kedua: OCP (Oracle Certified Professional). Anda akan menemui ujian dalam "Bab 20: Java Concurrency". Buku ini mempunyai kedua-dua soalan dan jawapan dengan penjelasan. Sebagai contoh: Lebih baik bersama: Java dan kelas Thread.  Bahagian VI - Jauhkan api!  - 3Ramai orang mungkin mula mengatakan bahawa soalan ini adalah satu lagi contoh hafalan kaedah. Di satu pihak, ya. Sebaliknya, anda boleh menjawab soalan ini dengan mengingati bahawa itu ExecutorServiceadalah sejenis "naik taraf" Executor. DanExecutorbertujuan untuk hanya menyembunyikan cara utas dicipta, tetapi ia bukan cara utama untuk melaksanakannya, iaitu, memulakan objek Runnablepada utas baharu. Itulah sebabnya tidak ada execute(Callable)— kerana dalam ExecutorService, Executorhanya menambah submit()kaedah yang boleh mengembalikan Futureobjek. Sudah tentu, kita boleh menghafal senarai kaedah, tetapi lebih mudah untuk membuat jawapan berdasarkan pengetahuan kita tentang sifat kelas itu sendiri. Dan berikut adalah beberapa bahan tambahan mengenai topik ini: Lebih baik bersama: Java dan kelas Thread. Bahagian I — Benang pelaksanaan Lebih baik bersama: Java dan kelas Benang. Bahagian II — Penyegerakan Lebih baik bersama: Java dan kelas Thread. Bahagian III — Interaksi Lebih Baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan Lebih baik bersama: Java dan kelas Thread. Bahagian V — Pelaksana, ThreadPool, Fork/Join
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION