ConcurrentHashMap trong Java là một class trong Concurrency Collection. Nó là một triển khai của hash table (bảng băm), hỗ trợ truy xuất và cập nhật đồng thời. ConcurrentHashMap được dùng trong môi trường đa luồng để tránh lỗi ConcurrentModificationException
.
ConcurrentHashMap
Nếu ta cố gắng chỉnh sửa một collection trong khi đang duyệt qua nó, ta sẽ gặp lỗi ConcurrentModificationException
. Java 1.5 đã bổ sung các class Concurrent trong gói java.util.concurrent
để giải quyết vấn đề này.
ConcurrentHashMap là một triển khai của Map, cho phép chúng ta chỉnh sửa Map ngay cả trong lúc duyệt nó. Các thao tác trên ConcurrentHashMap đều có tính an toàn luồng. ConcurrentHashMap không cho phép key và value có giá trị là null
.
Ví dụ về ConcurrentHashMap trong Java
Class ConcurrentHashMap tương tự như HashMap, chỉ khác ở chỗ nó có tính an toàn luồng và cho phép chỉnh sửa collection ngay trong khi duyệt.
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);
}
}
Output:
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)
Ta có thể thấy rõ rằng ConcurrentHashMap xử lý được việc thêm phần tử mới vào map ngay trong khi duyệt, trong khi HashMap lại gây ra lỗi . Cùng xem kỹ stack trace (thông tin các hàm gọi bị lỗi). Lỗi này được gây ra bởi câu lệnh sau.
String key = it1.next();
Điều này có nghĩa là phần tử mới đã được thêm vào HashMap, nhưng iterator (bộ lặp) lại gặp lỗi. Thực tế, iterator trên các đối tượng Collection có cơ chế fail-fast, tức là bất kỳ thay đổi nào về cấu trúc hay số lượng phần tử của collection đều sẽ gây ra lỗi ngay lập tức.
Làm sao iterator biết collection đã bị chỉnh sửa?
Ta đã lấy ra một set các key từ HashMap rồi duyệt qua set đó. HashMap có chứa một biến dùng để đếm số lần collection bị chỉnh sửa. Iterator sẽ sử dụng biến này mỗi khi ta gọi phương thức next() của nó để lấy entry tiếp theo.
/**
* 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;
Giờ hãy thay đổi code một chút để thoát khỏi vòng lặp của iterator ngay khi ta thêm một phần tử mới. Ta chỉ cần thêm một câu lệnh break
ngay sau lệnh put
.
if(key.equals("3")){
myMap.put(key+"new", "new3");
break;
}
Output với đoạn code trên như dưới:
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}
Điều gì xảy ra nếu giá trị của key bị chỉnh sửa?
Sẽ ra sao nếu ta không thêm một phần tử mới mà chỉ cập nhật một cặp key-value đã tồn tại? Liệu chương trình có gây ra lỗi không**?** Hãy cùng thay đổi code trong chương trình gốc và xem thử kết quả.
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");
Sẽ không có lỗi nào xảy ra, bởi vì dù collection đã bị chỉnh sửa, cấu trúc của nó vẫn được giữ nguyên.
Đọc thêm
Bạn có để ý các dấu ngoặc nhọn khi ta tạo đối tượng collection và iterator không? Chúng được gọi là generics, một tính năng rất mạnh mẽ trong việc kiểm tra kiểu (type-checking) tại lúc compile để loại bỏ lỗi ClassCastException
tại lúc runtime.
Hãy tìm đọc các bài viết khác của chúng tôi để hiểu thêm về generics cũng như các câu hỏi phỏng vấn về Java Collection cũng như kiểu thiết kế Iterator trong Java.