Panduan untuk Java Phaser

1. Ikhtisar

Pada artikel ini, kita akan melihat konstruksi Phaser dari paket java.util.concurrent . Ini adalah konstruksi yang sangat mirip dengan CountDownLatch yang memungkinkan kita mengoordinasikan eksekusi utas. Dibandingkan dengan CountDownLatch , ini memiliki beberapa fungsi tambahan.

The Phaser adalah penghalang yang nomor dinamis benang perlu menunggu sebelum melanjutkan eksekusi. Di CountDownLatch , nomor tersebut tidak dapat dikonfigurasi secara dinamis dan perlu diberikan saat kita membuat instance.

2. API Phaser

The Phaser memungkinkan kita untuk membangun logika di mana benang perlu menunggu penghalang sebelum pergi ke langkah berikutnya eksekusi .

Kami dapat mengoordinasikan beberapa fase eksekusi, menggunakan kembali instance Phaser untuk setiap fase program. Setiap fase dapat memiliki jumlah utas berbeda yang menunggu untuk maju ke fase lain. Kita akan melihat contoh penggunaan fase nanti.

Untuk berpartisipasi dalam koordinasi, utas perlu mendaftarkan () sendiri dengan instance Phaser . Perhatikan bahwa ini hanya meningkatkan jumlah pihak yang terdaftar, dan kami tidak dapat memeriksa apakah utas saat ini terdaftar - kami harus membuat subkelas implementasi untuk mendukung ini.

Utas memberi sinyal bahwa ia tiba di penghalang dengan memanggil arrivalAndAwaitAdvance () , yang merupakan metode pemblokiran. Ketika jumlah pihak yang datang sama dengan jumlah pihak yang terdaftar, maka pelaksanaan program akan dilanjutkan , dan jumlah tahap akan bertambah. Kita bisa mendapatkan nomor fase saat ini dengan memanggil metode getPhase () .

Saat utas menyelesaikan tugasnya, kita harus memanggil metode arriAndDeregister () untuk memberi sinyal bahwa utas saat ini tidak lagi diperhitungkan dalam fase khusus ini.

3. Menerapkan Logika Menggunakan API Phaser

Katakanlah kita ingin mengoordinasikan beberapa fase tindakan. Tiga utas akan memproses fase pertama, dan dua utas akan memproses fase kedua.

Kami akan membuat kelas LongRunningAction yang mengimplementasikan antarmuka Runnable :

class LongRunningAction implements Runnable { private String threadName; private Phaser ph; LongRunningAction(String threadName, Phaser ph) { this.threadName = threadName; this.ph = ph; ph.register(); } @Override public void run() { ph.arriveAndAwaitAdvance(); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } ph.arriveAndDeregister(); } }

Ketika kelas tindakan kita dibuat, kita mendaftar ke instance Phaser menggunakan metode register () . Ini akan menambah jumlah utas yang menggunakan Phaser spesifik itu .

Panggilan ke arriAndAwaitAdvance () akan menyebabkan utas saat ini menunggu di penghalang. Seperti yang sudah disebutkan, ketika jumlah pihak yang datang menjadi sama dengan jumlah pihak yang terdaftar, maka eksekusi akan dilanjutkan.

Setelah pemrosesan selesai, thread saat ini membatalkan pendaftarannya sendiri dengan memanggil metode arriAndDeregister () .

Mari buat kasus uji di mana kita akan memulai tiga utas LongRunningAction dan memblokir penghalang. Selanjutnya, setelah tindakan selesai, kami akan membuat dua utas LongRunningAction tambahan yang akan melakukan pemrosesan pada fase berikutnya.

Saat membuat instance Phaser dari utas utama, kami meneruskan 1 sebagai argumen. Ini sama dengan memanggil metode register () dari thread saat ini. Kami melakukan ini karena, saat kami membuat tiga utas pekerja, utas utama adalah koordinator, dan oleh karena itu Phaser perlu memiliki empat utas terdaftar padanya:

ExecutorService executorService = Executors.newCachedThreadPool(); Phaser ph = new Phaser(1); assertEquals(0, ph.getPhase());

Fase setelah inisialisasi sama dengan nol.

Kelas Phaser memiliki konstruktor di mana kita dapat meneruskan instance induk ke dalamnya. Ini berguna jika kami memiliki banyak pihak yang akan mengalami biaya pertentangan sinkronisasi yang sangat besar. Dalam situasi seperti itu, instance dari Phaser dapat diatur sehingga grup sub-phaser berbagi induk yang sama.

Selanjutnya, mari kita mulai tiga utas tindakan LongRunningAction , yang akan menunggu di penghalang sampai kita akan memanggil metode arriAndAwaitAdvance () dari utas utama.

Ingatlah bahwa kami telah menginisialisasi Phaser kami dengan 1 dan memanggil register () tiga kali lagi. Sekarang, tiga utas tindakan telah mengumumkan bahwa mereka telah tiba di penghalang, jadi satu panggilan lagi dari arriAndAwaitAdvance () diperlukan - yang dari utas utama:

executorService.submit(new LongRunningAction("thread-1", ph)); executorService.submit(new LongRunningAction("thread-2", ph)); executorService.submit(new LongRunningAction("thread-3", ph)); ph.arriveAndAwaitAdvance(); assertEquals(1, ph.getPhase());

Setelah menyelesaikan fase itu, metode getPhase () akan mengembalikan satu karena program selesai memproses langkah pertama eksekusi.

Katakanlah dua utas harus melakukan fase pemrosesan berikutnya. Kita dapat memanfaatkan Phaser untuk mencapai itu karena memungkinkan kita untuk mengonfigurasi secara dinamis jumlah utas yang harus menunggu di penghalang. Kami memulai dua utas baru, tetapi ini tidak akan terus dieksekusi hingga panggilan ke arriAndAwaitAdvance () dari utas utama (sama seperti dalam kasus sebelumnya):

executorService.submit(new LongRunningAction("thread-4", ph)); executorService.submit(new LongRunningAction("thread-5", ph)); ph.arriveAndAwaitAdvance(); assertEquals(2, ph.getPhase()); ph.arriveAndDeregister();

Setelah ini, metode getPhase () akan mengembalikan nomor fase yang sama dengan dua. Ketika kita ingin menyelesaikan program kita, kita perlu memanggil metode arriAndDeregister () karena utas utama masih terdaftar di Phaser. Ketika deregistrasi menyebabkan jumlah partai yang terdaftar menjadi nol, Phaser yang dihentikan. Semua panggilan ke metode sinkronisasi tidak akan diblokir lagi dan akan segera kembali.

Menjalankan program akan menghasilkan output berikut (kode sumber lengkap dengan pernyataan baris cetak dapat ditemukan di repositori kode):

This is phase 0 This is phase 0 This is phase 0 Thread thread-2 before long running action Thread thread-1 before long running action Thread thread-3 before long running action This is phase 1 This is phase 1 Thread thread-4 before long running action Thread thread-5 before long running action

Kami melihat bahwa semua utas menunggu eksekusi sampai penghalang terbuka. Fase eksekusi berikutnya dilakukan hanya jika eksekusi sebelumnya berhasil diselesaikan.

4. Kesimpulan

Dalam tutorial ini, kami melihat konstruksi Phaser dari java.util.concurrent dan kami mengimplementasikan logika koordinasi dengan beberapa fase menggunakan kelas Phaser .

Penerapan semua contoh dan cuplikan kode ini dapat ditemukan di proyek GitHub - ini adalah proyek Maven, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.