تعتبر فئة Java ConcurrentHashMap جزءًا من فئات مجموعة التحكم في التنفيذ المتزامن. إنها تمثل تنفيذًا لجدول التجزئة وتدعم الاسترجاع والتحديث المتزامنين. يُستخدم في بيئة متعددة الخيوط لتجنب استثناء ConcurrentModificationException.
ConcurrentHashMap
إذا حاولنا تعديل المجموعة أثناء تكرارها، سنحصل على استثناء `ConcurrentModificationException`. في Java 1.5، تم إدخال فئات Concurrent في حزمة `java.util.concurrent` لتجاوز هذ scenario. يُعد ConcurrentHashMap تنفيذًا لواجهة Map يسمح لنا بتعديل الخريطة أثناء التكرار. عمليات ConcurrentHashMap آمنة للتنفيذ المتزامن. لا تسمح ConcurrentHashMap بقيم null للمفاتيح والقيم.
مثال على Java ConcurrentHashMap
تشبه فئة `ConcurrentHashMap` فئة HashMap باستثناء أنها آمنة للتنفيذ المتزامن وتسمح بالتعديل أثناء التكرار.
package com.journaldev.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
//ConcurrentHashMap
Map<String,String> myMap = new ConcurrentHashMap<String,String>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
myMap.put("4", "1");
myMap.put("5", "1");
myMap.put("6", "1");
System.out.println("ConcurrentHashMap before iterator: "+myMap);
Iterator<String> it = myMap.keySet().iterator();
while(it.hasNext()){
String key = it.next();
if(key.equals("3")) myMap.put(key+"new", "new3");
}
System.out.println("ConcurrentHashMap after iterator: "+myMap);
//HashMap
myMap = new HashMap<String,String>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
myMap.put("4", "1");
myMap.put("5", "1");
myMap.put("6", "1");
System.out.println("HashMap before iterator: "+myMap);
Iterator<String> it1 = myMap.keySet().iterator();
while(it1.hasNext()){
String key = it1.next();
if(key.equals("3")) myMap.put(key+"new", "new3");
}
System.out.println("HashMap after iterator: "+myMap);
}
}
الناتج:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)
من الناتج واضح أن ConcurrentHashMap يعتني بالإدخال الجديد في الخريطة أثناء التكرار في حين يقوم HashMap بإلقاء ConcurrentModificationException
. دعونا نلقي نظرة عن كثب على تتبع الاستثناء. البيان التالي قد أثار استثناءً.
String key = it1.next();
هذا يعني أن الإدخال الجديد قد تم إدراجه في HashMap ولكن المكرر يفشل. في الواقع، المكرر على كائنات المجموعة هو فشل سريعًا أي أي تعديل في الهيكل أو عدد الإدخالات في كائن المجموعة سيثير الاستثناء.
كيف يعرف المكرر عن التعديل في المجموعة؟
لقد أخذنا مجموعة المفاتيح من HashMap ومن ثم التكرار عليها. HashMap يحتوي على متغير لحساب عدد التعديلات والمكرر يستخدمه عند استدعاء دالته next() للحصول على الإدخال التالي. HashMap.java
:
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
لنغير الشيفرة قليلاً للخروج من حلقة المكرر عند إدراج الإدخال الجديد. كل ما علينا فعله هو إضافة عبارة كسر بعد الاستدعاء put.
if(key.equals("3")){
myMap.put(key+"new", "new3");
break;
}
الناتج مع الشيفرة أعلاه:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}
ماذا يحدث إذا تم تعديل قيمة المفتاح؟
ماذا لو لم نقم بإضافة إدخال جديد ولكن قمنا بتحديث الزوج الحالي المفتاح-القيمة؟ هل سيتم طرح استثناء؟ دعونا نقوم بتغيير الشيفرة في البرنامج الأصلي ونتحقق من ذلك.
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");
لن يحدث أي استثناء لأن التجميع تم تعديله ولكن هيكله يظل كما هو.
قراءة إضافية
لاحظت تلك الزوايا أثناء إنشاء كائن التجميع والمدرج؟ يُطلق عليها الجنريكس وهي قوية جداً عندما يتعلق الأمر بفحص النوع في وقت الترجمة لإزالة استثناء ClassCastException في وقت التشغيل. اعرف المزيد حول الجنريكس في Java Generics Example. يجب عليك أيضًا قراءة Java Collections Interview Questions و Iterator Design Pattern in Java.
يمكنك مراجعة المزيد من أمثلة تجميع Java في GitHub Repository.
المرجع: API Doc
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java