مؤلف المذكرة هو Grzegorz Mirek، وهو مطور برامج من كراكوف (بولندا). بدأ التطوير في جاوة منذ حوالي 6 سنوات، بينما كان لا يزال في الجامعة، ومنذ ذلك الوقت وهو يعمل بلا كلل على صقل مهاراته في هذا المجال. إنه مهتم بشكل خاص بأداء JVM وتحسينه، وهو ما يكتب عنه بشكل أساسي في مدونته .
قصة اثنين من التكرارات: استراتيجيات التعديل التنافسي في Java - 1
تتضمن بعض أسئلة مقابلة Java الأكثر شيوعًا ما يلي: ما الفرق بين التكرارات السريعة والآمنة من الفشل؟ الإجابة الأكثر تبسيطًا على ذلك هي: يقوم المكرِّر سريع الفشل بطرح ConcurrentModificationException إذا تغيرت المجموعة أثناء التكرار، لكن المكرِّر الآمن من الفشل لا يفعل ذلك. على الرغم من أن هذا يبدو ذا معنى كبير، إلا أنه لا يزال من غير الواضح ما الذي يعنيه القائم بإجراء المقابلة بكلمة "آمنة من الفشل"؟ لا تحدد مواصفات لغة Java هذا المصطلح فيما يتعلق بالمكررات. ومع ذلك، هناك أربع استراتيجيات تعديل تنافسية.

تعديل تنافسي

أولاً، دعونا نحدد ما هو التعديل التنافسي (أو الموازي). لنفترض أن لدينا مجموعة وعندما يكون المكرِّر نشطًا، تحدث بعض التغييرات التي لا تأتي من هذا المكرِّر. في هذه الحالة، نحصل على تعديل تنافسي. اسمحوا لي أن أقدم لكم مثالاً بسيطًا: لنفترض أن لدينا عدة خيوط. يتكرر الخيط الأول، بينما يقوم الخيط الثاني بإدراج أو إزالة عناصر من نفس المجموعة. ومع ذلك، يمكننا الحصول على ConcurrentModificationException عند التشغيل في بيئة ذات ترابط واحد:
List<String> cities = new ArrayList<>();
cities.add(Warsaw);
cities.add(Prague);
cities.add(Budapest);

Iterator<String> cityIterator = cities.iterator();
cityIterator.next();
cities.remove(1);
cityIterator.next(); // генерирует ConcurrentModificationException

اخفاق سريع

جزء التعليمات البرمجية أعلاه هو مثال على مكرر سريع الفشل . كما ترون، تم طرح ConcurrentModificationException عند محاولة استرداد العنصر الثاني من المكرر . كيف يعرف المكرر أن المجموعة قد تم تعديلها منذ إنشائها؟ على سبيل المثال، قد تحتوي المجموعة على طابع تاريخ/وقت، على سبيل المثال lastModified . عند إنشاء مكرر، يجب عليك نسخ هذا الحقل وتخزينه في كائن مكرر. بعد ذلك، في كل مرة يتم استدعاء الأسلوب next() ، ستقوم ببساطة بمقارنة القيمة lastModified من المجموعة مع النسخة من المكرر. يتم استخدام نهج مشابه جدًا، على سبيل المثال، في تنفيذ فئة ArrayList . تحتوي على متغير مثيل modCount الذي يخزن عدد المرات التي تم فيها تعديل القائمة:
final void checkForComodification() {
   if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
}
من المهم ملاحظة أن التكرارات سريعة الفشل تعمل على أساس الأفضل، مما يعني أنه لا يوجد ضمان بأن ConcurrentModificationException سيتم طرحه في حالة إجراء تعديل متزامن. لذلك لا يجب الاعتماد عليها، بل يجب استخدامها لاكتشاف الأخطاء. توفر معظم المجموعات غير المتزامنة مكررات سريعة الفشل .

اتساق ضعيف

توفر معظم المجموعات المتزامنة في الحزمة java.util.concurrent (مثل ConcurrentHashMap ومعظم قوائم الانتظار ) تكرارات ضعيفة الاتساق. تم شرح معنى هذا المصطلح جيدًا في الوثائق :
  • ويمكن معالجتها بالتزامن مع العمليات الأخرى
  • إنهم لا يرمون أبدًا ConcurrentModificationException
  • وهي مضمونة لاجتياز العناصر الموجودة في وقت إنشاء المكرِّر مرة واحدة بالضبط، ويمكن (ولكن ليس مطلوبًا منها) أن تعكس التعديلات اللاحقة.

لمحة

باستخدام هذه الإستراتيجية، يرتبط المكرّر بحالة المجموعة في وقت إنشائها - وهذه لقطة من المجموعة. أي تغييرات يتم إجراؤها على المجموعة الأصلية تؤدي إلى إنشاء نسخة جديدة من بنية البيانات الأساسية. يؤدي هذا إلى ترك اللقطة الخاصة بنا دون تغيير، لذا فهي لا تعكس التغييرات التي طرأت على المجموعة بعد إنشاء المُكرِّر. هذه هي تقنية النسخ عند الكتابة (COW) القديمة الجيدة . إنه يحل مشكلة التعديلات المتزامنة تمامًا، لذلك لا يتم إنشاء ConcurrentModificationException باستخدام هذا الأسلوب. بالإضافة إلى ذلك، لا تدعم التكرارات العمليات التي تغير العناصر. تميل مجموعات النسخ عند الكتابة إلى أن تكون باهظة الثمن للاستخدام، ولكن من المنطقي استخدامها إذا كانت التغييرات تحدث بشكل أقل تكرارًا من اجتياز المكرر. ومن الأمثلة على ذلك الفئتان CopyOnWriteArrayList و CopyOnWriteArraySet .

سلوك غير محدد

قد تواجه سلوكًا غير محدد في أنواع المجموعات القديمة مثل Vector و Hashtable . كلاهما لديه مكررات قياسية سريعة الفشل ، ولكن بالإضافة إلى ذلك، فإنهما يسمحان باستخدام تطبيقات واجهة التعداد ، ولا يعرفان كيفية التصرف في حالة التعديل المتزامن. قد تواجه بعض العناصر المتكررة أو المفقودة، أو حتى بعض الاستثناءات الغريبة. من الأفضل عدم اللعب معهم!