0Pricing
Java Academy · บทเรียน

Iterator แบบหยุดเร็วกับแบบปลอดภัย

ทำความเข้าใจ ConcurrentModificationException พฤติกรรมแบบหยุดเร็ว และเวลาที่ควรใช้ iterator แบบสแนปช็อต

Iterator แบบหยุดเร็วกับแบบปลอดภัย เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

ตัววนซ้ำแบบตรวจจับข้อผิดพลาดทันทีและแบบทำงานต่ออย่างปลอดภัย

ตัววนซ้ำของ Java แบ่งประเภทตามพฤติกรรมเมื่อคอลเลกชันต้นทางถูกแก้ไขระหว่างการวนซ้ำ: ตัววนซ้ำแบบ ตรวจจับข้อผิดพลาดทันที จะโยนข้อยกเว้นทันที ส่วนตัววนซ้ำแบบ ทำงานต่ออย่างปลอดภัย จะทำงานต่อบนภาพข้อมูล ณ ขณะหนึ่ง

ตัววนซ้ำแบบตรวจจับข้อผิดพลาดทันที

คอลเลกชันมาตรฐานส่วนใหญ่ของ Java เช่น ArrayList, HashMap และ TreeSet ใช้ตัววนซ้ำแบบ ตรวจจับข้อผิดพลาดทันที ตัววนซ้ำเหล่านี้ติดตาม modCount ซึ่งเป็นตัวนับการเปลี่ยนแปลง การแก้ไขโครงสร้างใด ๆ ระหว่างการวนซ้ำจะทำให้เกิด ConcurrentModificationException

import java.util.*;

List<String> list = new ArrayList<>(List.of("a", "b", "c"));

try {
    for (String s : list) {
        list.add("x"); // structural modification — throws!
    }
} catch (ConcurrentModificationException e) {
    System.out.println("ConcurrentModificationException caught!");
}
// Same for HashMap, TreeMap, HashSet, etc.

เหตุผลที่มีการตรวจจับข้อผิดพลาดทันที

พฤติกรรมการตรวจจับข้อผิดพลาดทันทีช่วยในการแก้ไขข้อบกพร่อง โดยเปิดเผยข้อบกพร่องทันทีแทนที่จะปล่อยให้การวนซ้ำดำเนินต่อไปด้วยสถานะที่เสียหาย ทางเลือกที่ปล่อยให้ข้อมูลเสียหายโดยไม่มีการแจ้งเตือนจะแก้ไขข้อบกพร่องได้ยากกว่ามาก

// modCount is incremented on every structural change
// (add, remove, clear on ArrayList)
// Iterator records modCount on creation
// On each next(), Iterator checks: if current modCount != expected, throw CME

// This detects bugs like:
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5));
for (Integer n : nums) {
    if (n == 3) nums.remove(n); // bug caught immediately
}
// ConcurrentModificationException — not silent wrong results

การลบอย่างปลอดภัย: Iterator.remove()

การแก้ไขเพียงอย่างเดียวที่ปลอดภัยระหว่างการวนซ้ำโดยใช้ตัววนซ้ำคือ Iterator.remove() ซึ่งจะลบองค์ประกอบล่าสุดที่ส่งคืนและอัปเดต modCount

List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5,6));
Iterator<Integer> it = nums.iterator();

while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) it.remove(); // safe: updates modCount
}

System.out.println(nums); // [1, 3, 5]

removeIf: ทางเลือกสมัยใหม่

Java 8 เพิ่ม removeIf(Predicate) ให้กับคอลเลกชัน ซึ่งเป็นวิธีที่สะอาดกว่าในการลบองค์ประกอบที่ตรงตามเงื่อนไข โดยไม่ต้องจัดการตัววนซ้ำด้วยตนเอง

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Ann", "Charlie"));

// Modern: removeIf handles iteration internally
names.removeIf(name -> name.startsWith("A"));
System.out.println(names); // [Bob, Charlie]

// Equivalent but verbose iterator approach:
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("A")) it.remove();
}

ตัววนซ้ำแบบทำงานต่ออย่างปลอดภัย: CopyOnWriteArrayList

CopyOnWriteArrayList ใช้ตัววนซ้ำแบบ ทำงานต่ออย่างปลอดภัย ตัววนซ้ำจะเคลื่อนที่ผ่านภาพข้อมูลที่บันทึกไว้เมื่อสร้างตัววนซ้ำ การแก้ไขจึงไม่ส่งผลต่อการวนซ้ำที่กำลังดำเนินอยู่

import java.util.concurrent.CopyOnWriteArrayList;

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.addAll(List.of("a", "b", "c"));

for (String s : list) {
    list.add("x"); // no ConcurrentModificationException!
    System.out.print(s + " "); // prints a, b, c (snapshot)
}
System.out.println();
System.out.println(list); // [a, b, c, x, x, x] — modified copy

ตัววนซ้ำของ ConcurrentHashMap

ConcurrentHashMap ใช้ตัววนซ้ำที่มีความสอดคล้องแบบอ่อน ตัววนซ้ำจะแสดงสถานะ ณ จุดใดจุดหนึ่งระหว่างหรือหลังจากสร้างตัววนซ้ำแล้ว โดยจะไม่โยน CME และอาจเห็นหรือไม่เห็นการอัปเดตที่เกิดขึ้นพร้อมกันก็ได้

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1); map.put("b", 2); map.put("c", 3);

for (Map.Entry<String, Integer> e : map.entrySet()) {
    map.put("d", 4); // no CME — weakly consistent
    System.out.println(e.getKey() + "=" + e.getValue());
}
// May or may not print "d=4" — weakly consistent

Collections.synchronizedList

Collections.synchronizedList ไม่ใช่ตัววนซ้ำแบบทำงานต่ออย่างปลอดภัย แต่ยังคงใช้ตัววนซ้ำแบบตรวจจับข้อผิดพลาดทันที พร้อมทั้งบล็อกการเข้าถึงที่เกิดขึ้นพร้อมกัน คุณต้องทำให้การวนซ้ำทำงานแบบประสานกันด้วยตนเอง

import java.util.*;

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.addAll(List.of("a", "b", "c"));

// Must synchronize during iteration!
synchronized (syncList) {
    for (String s : syncList) {
        System.out.println(s);
    }
}
// Without the synchronized block, CME is still possible from another thread

รูปแบบตัววนซ้ำจากภาพข้อมูล

สร้างภาพข้อมูลด้วยตนเองเพื่อวนซ้ำผ่านคอลเลกชันที่เปลี่ยนแปลงได้อย่างปลอดภัยโดยไม่ต้องใช้การประสานกัน

import java.util.*;

List<String> original = new ArrayList<>(List.of("a", "b", "c"));

// Take a snapshot copy before iterating
List<String> snapshot = List.copyOf(original);

for (String s : snapshot) {
    // Safe to modify original during snapshot iteration
    original.remove(s);
    System.out.println("Removed: " + s);
}
System.out.println(original); // []

ข้อแลกเปลี่ยนของ CopyOnWrite

CopyOnWriteArrayList ทำงานต่อได้อย่างปลอดภัย แต่มีต้นทุนสูงสำหรับการเขียน การเปลี่ยนแปลงแต่ละครั้งจะสร้างสำเนาเต็มชุด

  • เหมาะสำหรับ: การอ่านจำนวนมากและการเขียนไม่บ่อย เช่น รายการตัวรับฟังเหตุการณ์
  • ไม่เหมาะสำหรับ: การเปลี่ยนแปลงบ่อยครั้งหรือคอลเลกชันขนาดใหญ่

สรุป: การตรวจจับข้อผิดพลาดทันทีเทียบกับการทำงานต่ออย่างปลอดภัย

สรุปการเปรียบเทียบ:

  • ตรวจจับข้อผิดพลาดทันที: ArrayList, HashMap, TreeMap จะโยน CME เมื่อมีการแก้ไข และตรวจสอบ modCount
  • ทำงานต่ออย่างปลอดภัย/มีความสอดคล้องแบบอ่อน: ConcurrentHashMap, CopyOnWriteArrayList จะไม่โยน CME และวนซ้ำผ่านภาพข้อมูลหรือข้อมูลที่มีการใช้งานพร้อมกัน
  • เมธอดสำหรับการแก้ไขอย่างปลอดภัย: Iterator.remove(), removeIf(), replaceAll()

ภาคปฏิบัติ: การล้างเซสชันที่หมดอายุ

การลบเซสชันที่หมดอายุออกจากรายการที่ใช้งานพร้อมกันอย่างปลอดภัย โดยใช้รูปแบบที่เหมาะสม

import java.util.*;
import java.util.concurrent.*;

class SessionManager {
    private final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>();

    record Session(String id, long expiresAt) {
        boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
    }

    void add(Session s) { sessions.add(s); }

    // Safe to call from multiple threads while iterating
    void purgeExpired() {
        sessions.removeIf(Session::isExpired);
    }

    List<Session> active() {
        return sessions.stream().filter(s -> !s.isExpired()).toList();
    }
}

ตรวจสอบความเข้าใจอย่างรวดเร็ว

การแก้ไข ArrayList ระหว่างการวนซ้ำจะโยนข้อยกเว้นใด

สรุป: ตัววนซ้ำแบบตรวจจับข้อผิดพลาดทันทีและแบบทำงานต่ออย่างปลอดภัย

ประเด็นสำคัญ:

  • ตัววนซ้ำแบบตรวจจับข้อผิดพลาดทันที เช่น ArrayList และ HashMap จะโยน ConcurrentModificationException เมื่อมีการแก้ไขพร้อมกัน
  • ตัววนซ้ำแบบทำงานต่ออย่างปลอดภัย เช่น CopyOnWriteArrayList และ ConcurrentHashMap อนุญาตให้แก้ไขได้โดยไม่โยนข้อยกเว้น
  • Iterator.remove() เป็นวิธีเดียวที่ปลอดภัยในการลบระหว่างการวนซ้ำแบบตรวจจับข้อผิดพลาดทันที
  • removeIf() เป็นทางเลือกสมัยใหม่ที่สะอาดกว่าการจัดการ Iterator.remove() ด้วยตนเอง
  • CopyOnWriteArrayList เหมาะสำหรับสถานการณ์ที่อ่านบ่อยและเขียนไม่บ่อย
  • ConcurrentHashMap มีความสอดคล้องแบบอ่อน จะไม่โยน CME และอาจเห็นหรือไม่เห็นการแก้ไขที่เกิดขึ้นพร้อมกันก็ได้

คำถามที่พบบ่อย

บทเรียน “Iterator แบบหยุดเร็วกับแบบปลอดภัย” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “Iterator แบบหยุดเร็วกับแบบปลอดภัย” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “Iterator แบบหยุดเร็วกับแบบปลอดภัย”

ทำความเข้าใจ ConcurrentModificationException พฤติกรรมแบบหยุดเร็ว และเวลาที่ควรใช้ iterator แบบสแนปช็อต คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “Iterator แบบหยุดเร็วกับแบบปลอดภัย” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. สัญญาของ Iterable และ Iterator
  2. การสร้าง Iterator แบบกำหนดเอง
  3. ListIterator และการวนดูแบบสองทิศทาง
  4. Iterator แบบหยุดเร็วกับแบบปลอดภัย
← กลับไปที่ Java Academy