המחלקה ConcurrentHashMap ב-Java היא חלק ממחלקות אוסף הקיבוציות. זו מימוש של טבלת גיבוב, אשר תומכת בשאילתה ועדכון קבוצתי. היא משמשת בסביבת תהליך רב-תרשימים כדי למנוע יוצא מן הכלל ConcurrentModificationException.
ConcurrentHashMap
אם ננסה לשנות את האוסף במהלך הדימות עליו, נקבל ConcurrentModificationException
. בגרסת ג'אווה 1.5 נכנסו מחלקות קונקרנטיות לחבילת java.util.concurrent
כדי להתמודד עם התרחשות זו. ConcurrentHashMap היא מימוש של Map שמאפשר לנו לשנות את ה-Map במהלך הדימות. פעולות ConcurrentHashMap הן בטוחות לתהליכים. ConcurrentHashMap אינה מאפשרת את הערכה של null למפתחות ולערכים.
דוגמא ל-ConcurrentHashMap ב-Java
מחלקת 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 אך ה-Iterator נכשל. למעשה, Iterator על אובייקטים Collection הוא פועל-כלפי כלומר כל שינוי במבנה או במספר הערכים באובייקט האוסף יפעיל את החריגה.
איך Iterator יודע על השינוי באוסף?
לקחנו את קבוצת המפתחות מתוך HashMap ואז עוברים עליה. HashMap מכילה משתנה לספירת מספר השינויים וה-Iterator משתמש בו כאשר אתה קורא לפונקציית 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;
בואו נשנה את הקוד קצת כדי לצאת מלולאת ה-Iterator כאשר אנו מכניסים את הערך החדש. כל שעלינו לעשות הוא להוסיף פקודת שבר אחרי קריאת הפונקציה 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");
לא יתרחש חריגה מאחר והאוסף הוחלף אך מבנהו נשאר זהה.
קריאה נוספת
האם שמרת על הסוגריים המקוננים האלה בעת יצירת עצם האוסף שלנו וה-Iterator? זה נקרא גנריקים וזה עוצמתי מאוד כאשר מדובר בבדיקת סוג בזמן קומפילציה כדי להסיר ClassCastException בזמן ריצה. למד עוד על גנריקים ב- דוגמה לגנריקים ב-Java. כדאי גם לקרוא על שאלות ראיון על אוספי Java ועל תבנית העיצוב Iterator ב-Java.
ניתן לבדוק דוגמאות נוספות של אוספי Java ב- מאגר הקוד שלנו ב-GitHub.
הפנייה: מסמך API
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java