02 grudnia 2011

O register() i bulkRegister(int) z j.u.concurrent.Phaser w Java 7

Przeglądając źródła j.u.concurrent.Phaser trafiłem na ciekawostkę, która z początku zaskoczyła mnie, ale kiedy się nad tym chwilę zastanowiłem, byłem nie mniej zdumiony swoim zdumieniem.

Phaser oferuje dwie metody do zarejestrowania uczestników faz - public int register() oraz public int bulkRegister(int parties).

Trudno zrozumieć, dlaczego dopiero przegląd kodu źródłowego Phaser uzmysłowiło mi, że pierwsza metoda - public int register() - jest specjalnym przypadkiem drugiej - public int bulkRegister(int parties), dla której parties = 1.

Mam wrażenie, że wynikało to z mojego błędnego zrozumienia działania tych metod, w którym sądziłem, że rejestracja jest w jakiś sposób związana z konkretnym wątkiem. Pamiętam, jak próbowałem przekazać to błędne rozumienie uczestnikom ostatniego spotkania Warszawa JUG i tylko ich czujność doprowadziła do mojego "Nie jestem pewien, jak to dokładnie działa", co w efekcie sprawiło, że zajrzałem do kodu źródłowego i teraz znam prawidłową odpowiedź (!)

A wystarczyło czytać javadoc dla Phaser ze zrozumieniem, w którym napisano:

"As is the case with most basic synchronization constructs, registration and deregistration affect only internal counts; they do not establish any further internal bookkeeping, so tasks cannot query whether they are registered. (However, you can introduce such bookkeeping by subclassing this class.)"

Proste? Teraz tak!

Poniższy przykład powinien pomóc zrozumieć problem bardziej. Od teraz już nie pomylę się i kolejna odsłona moich prezentacyjnych wyczynów wokół java.util.concurrent powinna nosić znamiona bardziej profesjonalnej :)
package pl.japila.java7.concurrent.phaser;

import java.util.concurrent.Phaser;

public class Phaser2Demo {

    public static void main(String[] args) throws Exception {
        final Phaser phaser = new Phaser();

        int parties = 3;
        for (int i = 0; i < parties; i++) {
            phaser.register();
        }

        assert 0 == phaser.getPhase();
        assert 3 == phaser.getRegisteredParties();
        assert 3 == phaser.getUnarrivedParties();

        Thread t;

        (t = new Thread() {
            public void run() {
                phaser.arrive(); // arrive and move on
            }
        }).start();

        t.join(); // awaits thread t's death

        assert 0 == phaser.getPhase();
        assert 3 == phaser.getRegisteredParties();
        assert 2 == phaser.getUnarrivedParties();

        (t = new Thread() {
            public void run() {
                phaser.arrive(); // arrive and move on
                phaser.arrive(); // arrive and move on
            }
        }).start();

        t.join(); // awaits thread t's death

        assert 1 == phaser.getPhase();
        assert 3 == phaser.getRegisteredParties();
        assert 3 == phaser.getUnarrivedParties();
    }

}
Coś niejasne? Wyjaśnić? Służę pomocą. Czym więcej pytań, tym więcej rozpoznania i tym lepsze moje zrozumienie tematu (oby i Twoje).