นากกายต้นฉบับคุมประตูงานที่มีบัตรผ่านจำนวนจำกัดและรับบัตรคืนหลังจบงาน

Java Semaphore: จำกัดงานพร้อมกันด้วย permit ที่คืนเสมอ

พี่โชว์Engineering

ถ้า service เปิดงานดาวน์โหลดร้อยงานพร้อมกันทั้งที่ฐานข้อมูลรับได้เพียงสิบ connection ปัญหาไม่ใช่ CPU อย่างเดียว แต่คือจำนวนคนที่กรูกันผ่านประตูครับ Semaphore แจก permit ตามจำนวนที่กำหนด งานต้องรับบัตรก่อนเข้าและคืนบัตรเมื่อออก ช่วยคุม concurrent access ที่จุดเดียว

หลักสำคัญคือคืน permit ใน finally หลังได้ permit แล้ว แม้งานข้างในจะโยน exception ไม่เช่นนั้น capacity จะรั่วจนระบบหยุดรับงาน ตัวอย่าง self-contained นี้ทดสอบทั้งกรณีเต็มและคืนแล้วรับใหม่; ระบบจริงต้องวาง code ที่ใช้ resource ไว้ใน block เดียวกัน

import java.util.concurrent.Semaphore;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Semaphore slots = new Semaphore(2, true);
        slots.acquire();
        slots.acquire();
        assert slots.availablePermits() == 0;
        assert !slots.tryAcquire();

        try {
            assert slots.isFair();
            // งานที่ใช้ทรัพยากรจริงจะอยู่ในขอบเขตนี้
        } finally {
            slots.release();
            slots.release();
        }
        assert slots.availablePermits() == 2;
        assert slots.tryAcquire();
        slots.release();
        System.out.println("Semaphore tests passed");
    }
}

Fairness ไม่ใช่คำสัญญาครอบทุก method

constructor ที่รับ true ขอคิวแบบ FIFO สำหรับการรอ permit เพื่อลด starvation แต่เอกสาร Oracle ระบุว่า tryAcquire() แบบไม่รอสามารถแซงคิวได้แม้ตั้ง fair ถ้าระบบต้องรักษาคิวจริง ให้ดู overload แบบ timed ที่เคารพ fairness และทดสอบภายใต้โหลดจริง นโยบาย fair ยังมีต้นทุน throughput; อย่าเปิดเพียงเพราะชื่อฟังดี

Semaphore จำกัดจำนวนงานที่ เข้า critical section แต่ไม่ได้จำกัดจำนวน request ที่ค้างรอ acquire() ถ้ามี traffic มากขึ้นไม่หยุด ต้องคุมขนาด queue, timeout และนโยบายตอบกลับควบคู่ มิฉะนั้นปัญหาย้ายจาก connection pool ไปเป็นกอง thread ที่รอ permit ครับ

Recap

  • กำหนด permit ตาม capacity ของทรัพยากรจริง
  • release() ใน finally หลัง acquire() สำเร็จ
  • แยก fairness ของ acquire() กับ tryAcquire()
  • มี timeout หรือ backpressure สำหรับคนที่รอหน้าประตู