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 consistentCollections.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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- สัญญาของ Iterable และ Iterator
- การสร้าง Iterator แบบกำหนดเอง
- ListIterator และการวนดูแบบสองทิศทาง
- Iterator แบบหยุดเร็วกับแบบปลอดภัย