นากกายต้นฉบับเลื่อนถาดงานเข้าสายพานด้านหนึ่งและหยิบออกอีกด้านตามลำดับ

Java ArrayDeque: คิวงานสองด้านโดยไม่ต้องใช้ LinkedList

พี่โชว์Engineering

ระบบงานเล็ก ๆ ที่ต้องหยิบงานเก่าสุดก่อน มักเริ่มจาก List.remove(0) ซึ่งต้องขยับสมาชิกที่เหลือทุกครั้งครับ ArrayDeque เป็น deque หรือคิวที่เพิ่มและหยิบได้ทั้งหัวและท้าย จึงใช้เป็น FIFO queue หรือ LIFO stack ได้ในคลาสเดียว เหมือนสายพานที่กำหนดได้ว่าจะรับและจ่ายกล่องทางไหน

เอกสาร Oracle ระบุว่าเป็น resizable array, โตตามต้องการ, ไม่รับ null และไม่ thread-safe หากหลาย thread แตะพร้อมกันต้องมี synchronization ภายนอก อย่าเปลี่ยนคิวที่หลาย worker ใช้มาเป็นคลาสนี้โดยตรง ตัวอย่างนี้ตั้งชื่อลงท้ายให้ชัดว่าเพิ่มท้ายและหยิบหัว แล้วทดสอบลำดับจริงด้วย assertions

import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    public static void main(String[] args) {
        Deque<String> jobs = new ArrayDeque<>();
        jobs.addLast("parse");
        jobs.addLast("validate");
        jobs.addLast("publish");
        assert jobs.removeFirst().equals("parse");
        assert jobs.removeFirst().equals("validate");
        assert jobs.removeFirst().equals("publish");
        assert jobs.isEmpty();

        Deque<Integer> history = new ArrayDeque<>();
        history.addFirst(1);
        history.addFirst(2);
        assert history.removeFirst() == 2;
        assert history.removeFirst() == 1;

        boolean rejectedNull = false;
        try { jobs.addLast(null); }
        catch (NullPointerException expected) { rejectedNull = true; }
        assert rejectedNull;
        System.out.println("ArrayDeque tests passed");
    }
}

เลือก method ให้โค้ดอ่านออก

addLast กับ removeFirst บอกทิศชัดกว่า push หรือ pop ในงาน queue; ถ้าเป็น stack ใช้ addFirst/removeFirst หรือ push/pop อย่างสม่ำเสมอ เมื่อคิวอาจว่าง เลือก pollFirst() เพื่อรับ null หรือ removeFirst() เพื่อให้ exception บอกว่า assumption พัง แต่เพราะสมาชิกจริงห้ามเป็น null การใช้ null เป็นผลของคิวว่างจึงไม่กำกวม

อย่าใช้ iterator เป็น snapshot: เอกสารอธิบายว่า iterator มัก fail-fast เมื่อคิวเปลี่ยน แต่ไม่มีสัญญาว่าจะตรวจทุก race ได้ ถ้าต้องแชร์งานข้าม thread ใช้ queue แบบ concurrent ที่ออกแบบมาสำหรับกรณีนั้น และเลือกนโยบาย backpressure ก่อนย้ายงานขึ้น production

Recap

  • FIFO ใช้ addLast คู่ removeFirst หรือ pollFirst
  • LIFO ใช้หัวด้านเดียวกันทั้งเพิ่มและหยิบ
  • ไม่ใส่ null และไม่แชร์ ArrayDeque ข้าม thread โดยไม่มีการคุม
  • เทสต์ลำดับรับงานรวมถึงคิวว่าง