มาสคอตแก้วกาแฟต้นฉบับกำกับนักวิ่งหลายคนผ่านประตูเป็นช่วง โดยมีคนหนึ่งออกและอีกคนเข้าร่วมกลางทาง

Java Phaser: Barrier ที่เพิ่ม–ลด Worker ระหว่างทางได้

พี่โชว์Engineering

CountDownLatch เหมาะกับประตูที่เปิดครั้งเดียว ส่วน CyclicBarrier เหมาะกับกลุ่มขนาดคงที่ที่กลับมาเจอกันหลายรอบ แต่ pipeline บางแบบมี worker เข้าหรือออกระหว่าง phase ครับ Phaser รวมแนวคิด barrier หลายรอบกับการ register/deregister แบบ dynamic

WHY: จำนวนผู้ร่วมงานไม่ควรถูกตรึงตั้งแต่เริ่มเสมอ

ลองนึกถึงทริปที่มีจุดนัดหลายจุด บางคนร่วมเฉพาะช่วงเช้า บางคนกลับก่อนเย็น Phaser ให้ participant ลงทะเบียนเมื่อรับงาน เรียก arriveAndAwaitAdvance เมื่อถึงจุดนัด และ arriveAndDeregister เมื่อไม่ร่วมช่วงถัดไป

จุดที่ต้องระวังคือ coordinator ต้องนับตัวเองเป็น party หากมันจะรอด้วย มิฉะนั้น phase อาจเดินก่อนกำหนด และทุก register ต้องมีเส้นทาง deregister แม้เกิด exception ไม่อย่างนั้นระบบจะรอ party ที่หายไปตลอดกาล

import java.util.concurrent.Phaser;

public class Main {
    public static void main(String[] args) throws Exception {
        Phaser phaser = new Phaser(1);
        StringBuilder trace = new StringBuilder();

        phaser.register();
        Thread worker = new Thread(() -> {
            trace.append("loaded;");
            phaser.arriveAndAwaitAdvance();
            trace.append("processed;");
            phaser.arriveAndDeregister();
        });
        worker.start();

        int phaseZero = phaser.arriveAndAwaitAdvance();
        assert phaseZero == 1 : "coordinator should enter phase 1";
        phaser.arriveAndDeregister();
        worker.join();

        assert trace.toString().equals("loaded;processed;") : trace;
        assert phaser.isTerminated() : "zero parties should terminate the phaser";
        System.out.println("phaser tests passed");
    }
}

HOW: ตั้งชื่อ Phase ให้คนอ่านตามทัน

เลข phase เริ่มจากศูนย์และเพิ่มเมื่อ parties ที่ลงทะเบียนมาถึงครบ แต่เลขเปล่าไม่สื่อความหมาย ให้ห่อ Phaser ในคลาส domain เช่น ImportCycle ที่มี method loaded, validated และ published แล้วบันทึก phase พร้อมชื่อขั้นตอน

ใช้ awaitAdvanceInterruptibly เมื่อ thread ต้องตอบสนองต่อ cancellation หรือ timeout การเรียก arriveAndAwaitAdvance ไม่ interruptible และอาจทำให้ shutdown ช้า หากงานล้มเหลว ให้กำหนดว่าจะ forceTermination เพื่อปล่อยผู้รอทั้งหมดหรือทำ compensation อย่าปล่อย worker อื่นค้างโดยไม่มีสัญญาณ

Phaser รองรับ tree เพื่อช่วย contention เมื่อ parties จำนวนมาก แต่ควรวัดก่อนเพิ่มโครงสร้าง สำหรับงานไม่กี่สิบ worker Phaser ตัวเดียวอ่านง่ายกว่า และถ้า workflow เป็น DAG ซับซ้อน ระบบ orchestration หรือ CompletableFuture อาจสื่อ dependency ได้ชัดกว่า barrier ทั่วไป

Recap

  • Phaser เหมาะกับหลาย phase และจำนวน party ที่เปลี่ยนได้
  • coordinator ที่รอต้อง register ตัวเอง
  • ทุก register ต้องมี deregister ในเส้นทางสำเร็จและล้มเหลว
  • ใช้ interruptible wait เมื่อมี deadline
  • ห่อเลข phase ด้วยชื่อ domain และวัดก่อนทำ hierarchy

ลองวาด lifecycle ของ party ทุกประเภทก่อนเขียนโค้ด ถ้ามีลูกศรเข้าแต่ไม่มีลูกศรออก นั่นคือ deadlock ที่รอเกิดครับ