5

Code:Làm thế nào để tránh HashMap "ConcurrentModificationException" trong khi thao tác `values ​​()` và `put()` trong các luồng đồng thời?

Tôi có một HashMap

private Map<K, V> map = new HashMap<>(); 

Một phương pháp sẽ đưa cặp K-V vào nó bằng cách gọi put(K,V).

Phương pháp khác muốn trích xuất một tập hợp các yếu tố ngẫu nhiên từ các giá trị của nó:

int size = map.size(); // size > 0 
V[] value_array = map.values().toArray(new V[size]); 
Random rand = new Random(); 
int start = rand.nextInt(size); int end = rand.nextInt(size); 
// return value_array[start .. end - 1] 

Hai phương pháp này được gọi là trong hai đề đồng thời khác nhau.


Lỗi:

tôi nhận được một lỗi ConcurrentModificationException:

at java.util.HashMap$HashIterator.nextEntry(Unknown Source) 
at java.util.HashMap$ValueIterator.next(Unknown Source) 
at java.util.AbstractCollection.toArray(Unknown Source) 

Dường như toArray() phương pháp trong một thread thực sự là iterating trên HashMap và put() sửa đổi trong chủ đề khác xảy ra.

Question: How to avoid "ConcurrentModificationException" while using HashMap.values().toArray() and HashMap.put() in concurrent threads?
Directly avoiding using values().toArray() in the second method is also OK.

+1

Execute mã mà truy cập vào 'map' trong một khối đồng bộ hóa: 'đồng bộ hóa (bản đồ) {...}' – Titus

+1

' đồng bộ hóa (bản đồ) {..} 'nên làm việc (nếu bạn áp dụng nó ở khắp mọi nơi). Collections.synchronizedMap sẽ không hoạt động. xem http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedMap%28java.util.Map%29 – Thilo

Trả lời

4

Bạn cần cung cấp một số mức độ đồng bộ để các cuộc gọi đến put bị chặn trong khi cuộc gọi toArray đang thực hiện và ngược lại. Có ba hai cách tiếp cận đơn giản:

  1. Quấn các cuộc gọi của bạn để puttoArray trong synchronized khối đồng bộ hóa trên các đối tượng khóa tương tự (có thể bản đồ riêng của mình hoặc một số đối tượng khác).
  2. Bật bản đồ của bạn thành một bản đồ đồng bộ sử dụng Collections.synchronizedMap()

    private Map<K, V> map = Collections.synchronizedMap(new HashMap<>()); 
    

  3. Sử dụng một ConcurrentHashMap thay vì một HashMap.

EDIT: Vấn đề với việc sử dụng Collections.synchronizedMap là một khi cuộc gọi đến values() lợi nhuận, bảo vệ đồng thời sẽ biến mất. Tại thời điểm đó, các cuộc gọi đến put()toArray() có thể thực thi đồng thời. A ConcurrentHashMap có vấn đề tương tự nhưng vẫn có thể được sử dụng. Từ các tài liệu cho ConcurrentHashMap.values():

The view's iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException , and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.

+0

@Thilo - Phải. Ba cách tiếp cận. :) –

+0

@Thilo Cảm ơn. Tuy nhiên, tôi đã đọc một số nhận xét rằng ConcurrentHashMap không nhất thiết phải giải quyết ConcurrentModificationException (nhưng không tìm được nguồn ngay bây giờ). Tại sao nó hoạt động trong trường hợp này? – hengxin

+1

Bạn cần 'values ​​()' để làm việc đa luồng, và Javadoc nói "Trình vòng lặp của khung nhìn là một trình lặp" yếu nhất quán "sẽ không bao giờ ném ConcurrentModificationException và đảm bảo cho các phần tử đi qua khi chúng tồn tại khi xây dựng trình lặp, và có thể (nhưng không được bảo đảm) phản ánh bất kỳ sửa đổi nào sau khi xây dựng. " – Thilo

0

Tôi sẽ sử dụng ConcurrentHashMap thay vì một HashMap và bảo vệ nó khỏi việc đọc đồng và sửa đổi bởi chủ đề khác nhau. Xem phần thực hiện bên dưới. Không thể cho thread 1 và thread 2 đọc và ghi cùng một lúc. Khi luồng 1 trích xuất các giá trị từ Bản đồ thành một mảng, tất cả các luồng khác gọi lệnh storeInMap (K, V) sẽ tạm dừng và đợi trên bản đồ cho đến khi chuỗi đầu tiên được thực hiện với đối tượng.

Lưu ý: Tôi không sử dụng phương pháp đồng bộ hóa trong ngữ cảnh này; Tôi không hoàn toàn loại trừ phương pháp đồng bộ hóa nhưng tôi sẽ sử dụng nó một cách thận trọng. Một phương pháp được đồng bộ hóa thực sự chỉ là cú pháp đường để nhận khóa trên 'this' và giữ nó trong suốt thời gian của phương thức để nó có thể làm tổn thương thông lượng.

private Map<K, V> map = new ConcurrentHashMap<K, V>(); 

// thread 1 
public V[] pickRandom() { 
    int size = map.size(); // size > 0 
    synchronized(map) { 
     V[] value_array = map.values().toArray(new V[size]); 
    } 
    Random rand = new Random(); 
    int start = rand.nextInt(size); 
    int end = rand.nextInt(size); 
    return value_array[start .. end - 1] 
} 

// thread 2 
public void storeInMap(K, V) { 
    synchronized(map) { 
     map.put(K,V); 
    } 
} 
+1

Tại sao khối 'syncronized' lại nếu bạn đang sử dụng' ConcurrentHashMap'? –

Các vấn đề liên quan