0Pricing
Java Academy · درس

المكررات Fail-Fast مقابل Fail-Safe

افهم ConcurrentModificationException وسلوك Fail-Fast ومتى تستخدم المكررات المعتمدة على لقطات

المكررات Fail-Fast مقابل Fail-Safe درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.

المكررات Fail-Fast وFail-Safe

تُصنّف مكررات Java وفق سلوكها عند تعديل المجموعة الأساسية أثناء التكرار: إذ تُطلق المكررات Fail-Fast استثناءً فورًا، بينما تتابع المكررات Fail-Safe العمل على لقطة.

المكررات Fail-Fast

تستخدم معظم مجموعات Java القياسية، مثل ArrayList وHashMap وTreeSet، مكررات Fail-Fast. إذ تتتبع 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.

سبب وجود Fail-Fast

يُعد سلوك Fail-Fast أداةً لتصحيح الأخطاء؛ فهو يكشف الأخطاء فورًا بدلًا من السماح بمتابعة التكرار بحالة تالفة. أما البديل، أي التلف الصامت، فأصعب بكثير في تصحيحه.

// 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 هو 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) إلى Collection، وهي طريقة أوضح لإزالة العناصر التي تطابق شرطًا دون إدارة المكرّر يدويًا.

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();
}

المكررات Fail-Safe: CopyOnWriteArrayList

يستخدم CopyOnWriteArrayList مكرّرًا Fail-Safe. إذ يكرّر العناصر الموجودة في لقطة أُخذت عند إنشاء المكرّر، ولا تؤثر التعديلات في التكرار الجاري.

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 ليست Fail-Safe — فهي لا تزال تستخدم مكررات Fail-Fast، لكنها تحظر الوصول المتزامن. ويجب عليك إجراء المزامنة يدويًا أثناء التكرار.

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 بكونها Fail-Safe، لكنها مكلفة عند الكتابة. إذ ينشئ كل تعديل نسخة كاملة.

  • مناسبة لـ: عمليات القراءة الكثيرة والكتابة النادرة، مثل قوائم مستمعي الأحداث
  • غير مناسبة لـ: التعديلات المتكررة والمجموعات الكبيرة

ملخص: Fail-Fast مقابل Fail-Safe

ملخص المقارنة:

  • Fail-Fast: ArrayList وHashMap وTreeMap — تطلق CME عند التعديل وتتحقق من modCount
  • Fail-Safe/متّسقة بشكل ضعيف: 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 أثناء التكرار؟

مراجعة: المكررات Fail-Fast وFail-Safe

أهم النقاط:

  • تُطلق المكررات Fail-Fast، مثل ArrayList وHashMap، ConcurrentModificationException عند حدوث تعديل متزامن
  • تسمح المكررات Fail-Safe، مثل CopyOnWriteArrayList وConcurrentHashMap، بالتعديل دون إطلاق استثناء
  • يُعد Iterator.remove() الطريقة الآمنة الوحيدة للإزالة أثناء التكرار باستخدام Fail-Fast
  • يُعد removeIf() البديل الحديث والأوضح للاستخدام اليدوي لـ Iterator.remove()
  • تُعد CopyOnWriteArrayList مناسبة للسيناريوهات التي تكثر فيها القراءة وتندر فيها الكتابة
  • إن ConcurrentHashMap متّسقة بشكل ضعيف — لا تطلق CME، وقد ترى التعديلات المتزامنة أو لا تراها

الأسئلة الشائعة

هل درس «المكررات Fail-Fast مقابل Fail-Safe» مجاني؟

نعم — نص درس «المكررات Fail-Fast مقابل Fail-Safe» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.

ماذا ستتعلم في «المكررات Fail-Fast مقابل Fail-Safe»؟

افهم ConcurrentModificationException وسلوك Fail-Fast ومتى تستخدم المكررات المعتمدة على لقطات تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟

لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «المكررات Fail-Fast مقابل Fail-Safe»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟

نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. عقود Iterable وIterator
  2. تطبيق Iterator مخصص
  3. ListIterator والتنقل ثنائي الاتجاه
  4. المكررات Fail-Fast مقابل Fail-Safe
← العودة إلى Java Academy