CodeGym /Java Blog /Acak /Lebih baik bersama: Java dan kelas Thread. Bagian VI — Te...
John Squirrels
Level 41
San Francisco

Lebih baik bersama: Java dan kelas Thread. Bagian VI — Tembak!

Dipublikasikan di grup Acak

Perkenalan

Utas adalah hal yang menarik. Dalam ulasan sebelumnya, kami melihat beberapa alat yang tersedia untuk mengimplementasikan multithreading. Mari kita lihat hal menarik apa lagi yang bisa kita lakukan. Pada titik ini, kami tahu banyak. Misalnya, dari " Lebih baik bersama: Java dan kelas Utas. Bagian I — Utas eksekusi ", kita tahu bahwa kelas Utas mewakili utas eksekusi. Kami tahu bahwa utas melakukan beberapa tugas. Jika kita ingin tugas kita dapat run, maka kita harus menandai utasnya dengan Runnable. Lebih baik bersama: Java dan kelas Thread.  Bagian VI — Tembak!  - 1Yang perlu diingat, kita bisa 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 bahwa kami memiliki sesuatu yang disebut kunci. Kami mempelajarinya di " Lebih baik bersama: Java dan kelas Utas. Bagian II — Sinkronisasi . Jika satu utas memperoleh kunci, utas lain yang mencoba memperoleh kunci akan dipaksa menunggu hingga 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 pikir sudah waktunya untuk berbicara tentang hal menarik apa lagi yang bisa kita lakukan.

Semafor

Cara paling sederhana untuk mengontrol berapa banyak utas yang dapat berjalan secara bersamaan adalah semaphore. Ini seperti sinyal kereta api. Hijau berarti melanjutkan. Merah berarti menunggu. Tunggu apa dari semafor? Mengakses. Untuk mendapatkan akses, kita harus mendapatkannya. Dan ketika akses tidak lagi dibutuhkan, kita harus memberikannya atau melepaskannya. Mari kita lihat bagaimana ini bekerja. Kita perlu mengimpor 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 kita memahami cara kerja semaphore. Yang terpenting adalah jika kita ingin mendapatkan akses, maka semaphore harus memiliki izin angka positif. Hitungan ini dapat diinisialisasi ke angka negatif. Dan kami dapat meminta (memperoleh) lebih dari 1 izin.

CountDownLatch

Mekanisme selanjutnya adalah CountDownLatch. Tidak mengherankan, ini adalah kait dengan hitungan mundur. Di sini kita memerlukan pernyataan impor yang sesuai untuk java.util.concurrent.CountDownLatchkelas. Ini seperti lomba lari kaki, di mana semua orang berkumpul di garis start. Dan begitu semua orang siap, semua orang menerima sinyal awal pada waktu yang sama dan mulai secara bersamaan. 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();
 	}
}
Pertama, kita beri tahu latch terlebih dahulu ke countDown(). Google mendefinisikan hitungan mundur sebagai "tindakan menghitung angka dalam urutan terbalik ke nol". Dan kemudian kami memberi tahu gerendel ke await(), yaitu menunggu hingga penghitung menjadi nol. Menariknya, ini adalah penghitung satu kali. Dokumentasi Java mengatakan, "Ketika utas harus berulang kali menghitung mundur dengan cara ini, alih-alih gunakan CyclicBarrier". Dengan kata lain, jika Anda memerlukan penghitung yang dapat digunakan kembali, Anda memerlukan opsi lain: CyclicBarrier.

CyclicBarrier

Seperti namanya, CyclicBarrieradalah penghalang "dapat digunakan kembali". Kita perlu mengimpor java.util.concurrent.CyclicBarrierkelas. Mari kita lihat sebuah 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, utas menjalankan awaitmetode, yaitu menunggu. Dalam hal ini, nilai penghalang berkurang. Penghalang dianggap rusak ( barrier.isBroken()) saat hitungan mundur mencapai nol. Untuk mengatur ulang penghalang, Anda perlu memanggil reset()metode yang CountDownLatchtidak ada.

Penukar

Mekanisme selanjutnya adalah Exchanger. Dalam konteks ini, Pertukaran adalah titik sinkronisasi di mana hal-hal yang berubah dipertukarkan atau ditukar. Seperti yang Anda harapkan, an Exchangeradalah kelas yang melakukan pertukaran atau penukaran. Mari kita lihat contoh paling sederhana:

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 memulai dua utas. Masing-masing menjalankan metode pertukaran dan menunggu utas lainnya juga menjalankan metode pertukaran. Dengan demikian, utas bertukar argumen yang diteruskan. Menarik. Apakah itu tidak mengingatkan Anda pada sesuatu? Ini mengingatkan pada SynchronousQueue, yang terletak di jantung CachedThreadPool. Untuk kejelasan, berikut ini contohnya:

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 bahwa ketika sebuah thread baru dimulai, ia akan menunggu, karena antrian akan kosong. Dan kemudian utas utama memasukkan string "Pesan" ke dalam antrian. Terlebih lagi, itu juga akan berhenti sampai string ini diterima dari antrian. Anda juga dapat membaca " SynchronousQueue vs Exchanger " untuk mengetahui lebih lanjut tentang topik ini.

Phaser

Kami telah menyimpan yang terbaik untuk yang terakhir — Phaser. Kita perlu mengimpor java.util.concurrent.Phaserkelas. Mari kita lihat contoh sederhana:

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 tersebut mengilustrasikan bahwa ketika menggunakan Phaser, penghalang akan pecah ketika jumlah registrasi sesuai dengan jumlah kedatangan di penghalang. Anda bisa lebih mengenalnya Phaserdengan membaca artikel GeeksforGeeks ini .

Ringkasan

Seperti yang Anda lihat dari contoh ini, ada berbagai cara untuk menyinkronkan utas. Sebelumnya, saya mencoba mengingat kembali aspek multithreading. Saya harap angsuran sebelumnya dalam seri ini bermanfaat. Beberapa orang mengatakan bahwa jalan menuju multithreading dimulai dengan buku "Java Concurrency in Practice". Meskipun dirilis pada tahun 2006, orang mengatakan bahwa buku ini cukup mendasar dan masih relevan hingga saat ini. Misalnya, Anda dapat membaca diskusi di sini: Apakah "Java Concurrency In Practice" masih berlaku? . Juga bermanfaat untuk membaca tautan dalam diskusi. Misalnya, ada tautan ke buku The Well-Grounded Java Developer , dan kami akan menyebutkan secara khusus Bab 4. Konkurensi modern . Ada juga seluruh ulasan tentang topik ini:Apakah "Java Concurrency in Practice" Masih Berlaku di Era Java 8? Artikel itu juga menawarkan saran tentang apa lagi yang harus dibaca untuk benar-benar memahami topik ini. Setelah itu, Anda bisa melihat buku bagus seperti OCA/OCP Java SE 8 Programmer Practice Tests . Kami tertarik dengan akronim kedua: OCP (Oracle Certified Professional). Anda akan menemukan tes di "Bab 20: Java Concurrency". Buku ini memiliki pertanyaan dan jawaban dengan penjelasan. Misalnya: Lebih baik bersama: Java dan kelas Thread.  Bagian VI — Tembak!  - 3Banyak orang mungkin mulai mengatakan bahwa pertanyaan ini adalah contoh lain dari menghafal metode. Di satu sisi, ya. Di sisi lain, Anda dapat menjawab pertanyaan ini dengan mengingat bahwa itu ExecutorServiceadalah semacam "peningkatan" dari Executor. DanExecutordimaksudkan untuk menyembunyikan cara utas dibuat, tetapi ini bukan cara utama untuk mengeksekusinya, yaitu memulai objek Runnablepada utas baru. Itu sebabnya tidak ada execute(Callable)— karena dalam ExecutorService, metode yang Executorhanya menambahkan submit()yang dapat mengembalikan Futureobjek. Tentu saja, kita dapat mengingat daftar metode, tetapi jauh lebih mudah untuk membuat jawaban berdasarkan pengetahuan kita tentang sifat kelas itu sendiri. Dan berikut adalah beberapa materi tambahan tentang topik ini: Lebih baik bersama: Java dan kelas Thread. Bagian I — Utas eksekusi Lebih baik bersama: Java dan kelas Utas. Bagian II — Sinkronisasi Lebih baik bersama: Java dan kelas Thread. Bagian III — Interaksi Bersama yang lebih baik: Java dan kelas Thread. Bagian IV — Callable, Future, dan teman Lebih baik bersama: Java dan kelas Thread. Bagian V — Pelaksana, ThreadPool, Fork/Bergabung
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION