113

Trong javadoc cho ConcurrentHashMap như sau:Đang lặp lại chuỗi giá trị ConcurrentHashMap có an toàn không?

hoạt động Retrieval (bao gồm get) thường không chặn, vì vậy có thể chồng chéo với các hoạt động cập nhật (kể cả đặt và gỡ bỏ). Retrievals phản ánh kết quả của các hoạt động cập nhật mới nhất được hoàn thành khi họ khởi động. Đối với các phép toán tổng hợp như putAll và rõ ràng, các lần truy xuất đồng thời có thể phản ánh việc chèn hoặc loại bỏ chỉ một số mục nhập. Tương tự, các Iterator và Enumerations trả về các phần tử phản ánh trạng thái của bảng băm tại một số điểm tại hoặc từ khi tạo ra vòng lặp/liệt kê. Họ không ném ConcurrentModificationException. Tuy nhiên, các trình lặp được thiết kế để chỉ được sử dụng bởi một luồng tại một thời điểm.

Điều đó có nghĩa là gì? Điều gì sẽ xảy ra nếu tôi cố lặp lại bản đồ với hai luồng cùng một lúc? Điều gì xảy ra nếu tôi đặt hoặc xóa một giá trị khỏi bản đồ trong khi lặp lại nó?

Trả lời

147

Điều đó có nghĩa là gì?

Điều đó có nghĩa là mỗi trình lặp mà bạn nhận được từ ConcurrentHashMap được thiết kế để sử dụng bởi một chuỗi duy nhất và không được chuyển qua. Điều này bao gồm đường cú pháp mà vòng lặp for-each cung cấp.

Điều gì sẽ xảy ra nếu tôi cố lặp lại bản đồ với hai chuỗi cùng một lúc?

Nó sẽ hoạt động như mong đợi nếu mỗi chủ đề sử dụng trình lặp của riêng nó.

Điều gì xảy ra nếu tôi đặt hoặc xóa giá trị khỏi bản đồ trong khi lặp lại?

Đảm bảo rằng mọi thứ sẽ không bị hỏng nếu bạn làm điều này (đó là một phần của "đồng thời" trong phương tiện ConcurrentHashMap). Tuy nhiên, không có gì đảm bảo rằng một luồng sẽ thấy các thay đổi đối với bản đồ mà luồng khác thực hiện (mà không cần có một trình lặp mới từ bản đồ). Trình vòng lặp được đảm bảo để phản ánh trạng thái của bản đồ tại thời điểm tạo bản đồ. Các thay đổi khác có thể được phản ánh trong trình lặp, nhưng chúng không nhất thiết phải như vậy.

Tóm lại, một tuyên bố như

for (Object o : someConcurrentHashMap.entrySet()) { 
    // ... 
} 

sẽ ổn thôi (hoặc ít nhất là an toàn) gần như mỗi khi bạn nhìn thấy nó.

+0

Vậy điều gì sẽ xảy ra nếu trong quá trình lặp lại, một chuỗi khác đã xóa một đối tượng o10 khỏi bản đồ? Tôi vẫn có thể thấy o10 trong lần lặp lại ngay cả khi nó đã bị xóa? @Waldheinz – Alex

+0

Như đã nêu ở trên, nó thực sự không được chỉ định nếu một trình vòng lặp hiện có sẽ phản ánh các thay đổi sau này đối với bản đồ. Vì vậy, tôi không biết, và bởi đặc điểm kỹ thuật không ai làm (không nhìn vào mã, và có thể thay đổi với mỗi cập nhật của thời gian chạy). Vì vậy, bạn không thể dựa vào nó. – Waldheinz

+4

Nhưng tôi vẫn có một 'ConcurrentModificationException' trong khi lặp lại một' ConcurrentHashMap', tại sao? –

5

This có thể cung cấp cho bạn một cái nhìn sâu sắc tốt

ConcurrentHashMap đạt được đồng thời cao hơn bởi hơi thư giãn những lời hứa nó làm cho người gọi. Một hoạt động truy xuất sẽ trả về giá trị được chèn vào bởi thao tác chèn hoàn thành gần đây nhất, và cũng có thể trả về một giá trị được thêm vào bởi một thao tác chèn đồng thời đang tiến hành (nhưng không có trường hợp nào nó trả về một kết quả vô nghĩa). Các bộ lặp được trả về bởi ConcurrentHashMap.iterator() sẽ trả về mỗi phần tử một lần và sẽ không bao giờ ném ConcurrentModificationException, nhưng có thể hoặc không thể phản ánh việc chèn hoặc xóa đã xảy ra kể từ khi trình lặp được xây dựng. Không cần khóa toàn bộ bàn (hoặc thậm chí có thể) để cung cấp an toàn luồng khi lặp lại bộ sưu tập. ConcurrentHashMap có thể được sử dụng để thay thế cho SynchronMapMap hoặc Hashtable trong bất kỳ ứng dụng nào không phụ thuộc vào khả năng khóa toàn bộ bảng để ngăn cập nhật.

Về vấn đề này:

Tuy nhiên, vòng lặp được thiết kế để được sử dụng bởi chỉ có một thread tại một thời điểm.

Có nghĩa là, trong khi sử dụng trình lặp do ConcurrentHashMap tạo ra trong hai chủ đề an toàn, nó có thể gây ra kết quả không mong muốn trong ứng dụng.

8

Điều đó có nghĩa là bạn không nên chia sẻ đối tượng trình lặp trong số nhiều chuỗi. Tạo nhiều trình lặp và sử dụng chúng đồng thời trong các luồng riêng biệt là tốt.

+0

Bất kỳ lý do nào bạn không viết hoa I trong Iterator?Vì nó là tên của lớp, nó có thể ít khó hiểu hơn. –

+1

@Bill Michell, bây giờ chúng ta đang ở trong ngữ nghĩa của việc đăng tải nghi thức. Tôi nghĩ rằng anh ta nên làm cho Iterator một liên kết quay lại javadoc cho một Iterator, hoặc ít nhất là đặt nó bên trong các chú giải mã nội tuyến ('). –

17

Bạn có thể sử dụng lớp này để kiểm tra hai luồng truy cập và một đột biến instance chia sẻ của ConcurrentHashMap:

import java.util.Map; 
import java.util.Random; 
import java.util.UUID; 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

public class ConcurrentMapIteration 
{ 
    private final Map<String, String> map = new ConcurrentHashMap<String, String>(); 

    private final static int MAP_SIZE = 100000; 

    public static void main(String[] args) 
    { 
    new ConcurrentMapIteration().run(); 
    } 

    public ConcurrentMapIteration() 
    { 
    for (int i = 0; i < MAP_SIZE; i++) 
    { 
     map.put("key" + i, UUID.randomUUID().toString()); 
    } 
    } 

    private final ExecutorService executor = Executors.newCachedThreadPool(); 

    private final class Accessor implements Runnable 
    { 
    private final Map<String, String> map; 

    public Accessor(Map<String, String> map) 
    { 
     this.map = map; 
    } 

    @Override 
    public void run() 
    { 
     for (Map.Entry<String, String> entry : this.map.entrySet()) 
     { 
     System.out.println(
      Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']' 
     ); 
     } 
    } 
    } 

    private final class Mutator implements Runnable 
    { 

    private final Map<String, String> map; 
    private final Random random = new Random(); 

    public Mutator(Map<String, String> map) 
    { 
     this.map = map; 
    } 

    @Override 
    public void run() 
    { 
     for (int i = 0; i < 100; i++) 
     { 
     this.map.remove("key" + random.nextInt(MAP_SIZE)); 
     this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); 
     System.out.println(Thread.currentThread().getName() + ": " + i); 
     } 
    } 
    } 

    private void run() 
    { 
    Accessor a1 = new Accessor(this.map); 
    Accessor a2 = new Accessor(this.map); 
    Mutator m = new Mutator(this.map); 

    executor.execute(a1); 
    executor.execute(m); 
    executor.execute(a2); 
    } 
} 

Không có ngoại lệ sẽ được ném ra.

Chia sẻ iterator giống nhau giữa đề accessor có thể dẫn đến bế tắc:

import java.util.Iterator; 
import java.util.Map; 
import java.util.Random; 
import java.util.UUID; 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

public class ConcurrentMapIteration 
{ 
    private final Map<String, String> map = new ConcurrentHashMap<String, String>(); 
    private final Iterator<Map.Entry<String, String>> iterator; 

    private final static int MAP_SIZE = 100000; 

    public static void main(String[] args) 
    { 
    new ConcurrentMapIteration().run(); 
    } 

    public ConcurrentMapIteration() 
    { 
    for (int i = 0; i < MAP_SIZE; i++) 
    { 
     map.put("key" + i, UUID.randomUUID().toString()); 
    } 
    this.iterator = this.map.entrySet().iterator(); 
    } 

    private final ExecutorService executor = Executors.newCachedThreadPool(); 

    private final class Accessor implements Runnable 
    { 
    private final Iterator<Map.Entry<String, String>> iterator; 

    public Accessor(Iterator<Map.Entry<String, String>> iterator) 
    { 
     this.iterator = iterator; 
    } 

    @Override 
    public void run() 
    { 
     while(iterator.hasNext()) { 
     Map.Entry<String, String> entry = iterator.next(); 
     try 
     { 
      String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; 
     } catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 

     } 
    } 
    } 

    private final class Mutator implements Runnable 
    { 

    private final Map<String, String> map; 
    private final Random random = new Random(); 

    public Mutator(Map<String, String> map) 
    { 
     this.map = map; 
    } 

    @Override 
    public void run() 
    { 
     for (int i = 0; i < 100; i++) 
     { 
     this.map.remove("key" + random.nextInt(MAP_SIZE)); 
     this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); 
     } 
    } 
    } 

    private void run() 
    { 
    Accessor a1 = new Accessor(this.iterator); 
    Accessor a2 = new Accessor(this.iterator); 
    Mutator m = new Mutator(this.map); 

    executor.execute(a1); 
    executor.execute(m); 
    executor.execute(a2); 
    } 
} 

Ngay sau khi bạn bắt đầu chia sẻ cùng Iterator<Map.Entry<String, String>> giữa các accessor và mutator đề java.lang.IllegalStateException s sẽ bắt đầu nảy lên.

import java.util.Iterator; 
import java.util.Map; 
import java.util.Random; 
import java.util.UUID; 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

public class ConcurrentMapIteration 
{ 
    private final Map<String, String> map = new ConcurrentHashMap<String, String>(); 
    private final Iterator<Map.Entry<String, String>> iterator; 

    private final static int MAP_SIZE = 100000; 

    public static void main(String[] args) 
    { 
    new ConcurrentMapIteration().run(); 
    } 

    public ConcurrentMapIteration() 
    { 
    for (int i = 0; i < MAP_SIZE; i++) 
    { 
     map.put("key" + i, UUID.randomUUID().toString()); 
    } 
    this.iterator = this.map.entrySet().iterator(); 
    } 

    private final ExecutorService executor = Executors.newCachedThreadPool(); 

    private final class Accessor implements Runnable 
    { 
    private final Iterator<Map.Entry<String, String>> iterator; 

    public Accessor(Iterator<Map.Entry<String, String>> iterator) 
    { 
     this.iterator = iterator; 
    } 

    @Override 
    public void run() 
    { 
     while (iterator.hasNext()) 
     { 
     Map.Entry<String, String> entry = iterator.next(); 
     try 
     { 
      String st = 
       Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; 
     } catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 

     } 
    } 
    } 

    private final class Mutator implements Runnable 
    { 

    private final Random random = new Random(); 

    private final Iterator<Map.Entry<String, String>> iterator; 

    private final Map<String, String> map; 

    public Mutator(Map<String, String> map, Iterator<Map.Entry<String, String>> iterator) 
    { 
     this.map = map; 
     this.iterator = iterator; 
    } 

    @Override 
    public void run() 
    { 
     while (iterator.hasNext()) 
     { 
     try 
     { 
      iterator.remove(); 
      this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); 
     } catch (Exception ex) 
     { 
      ex.printStackTrace(); 
     } 
     } 

    } 
    } 

    private void run() 
    { 
    Accessor a1 = new Accessor(this.iterator); 
    Accessor a2 = new Accessor(this.iterator); 
    Mutator m = new Mutator(map, this.iterator); 

    executor.execute(a1); 
    executor.execute(m); 
    executor.execute(a2); 
    } 
} 
+0

Bạn có chắc chắn về 'Chia sẻ cùng một trình lặp giữa các chuỗi truy cập có thể dẫn đến bế tắc' không? Tài liệu nói đọc không bị chặn và tôi đã thử chương trình của bạn và không có bế tắc nào xảy ra. Mặc dù kết quả lặp lại sẽ sai. – Tony

4

Điều đó có nghĩa là gì?

Điều đó có nghĩa là bạn không nên cố sử dụng cùng một trình lặp trong hai luồng. Nếu bạn có hai luồng cần lặp qua các khóa, giá trị hoặc các mục, thì mỗi luồng sẽ tạo và sử dụng các trình vòng lặp của riêng chúng.

Điều gì sẽ xảy ra nếu tôi cố lặp lại bản đồ với hai chuỗi cùng một lúc?

Không hoàn toàn rõ ràng điều gì sẽ xảy ra nếu bạn phá vỡ quy tắc này. Bạn chỉ có thể nhận được hành vi khó hiểu, theo cùng một cách mà bạn làm nếu (ví dụ) hai chủ đề cố gắng đọc từ đầu vào tiêu chuẩn mà không cần đồng bộ hóa. Bạn cũng có thể nhận được hành vi không an toàn cho luồng.

Nhưng nếu hai chủ đề sử dụng trình lặp khác nhau, bạn sẽ ổn.

Điều gì xảy ra nếu tôi đặt hoặc xóa giá trị khỏi bản đồ trong khi lặp lại?

Đó là vấn đề riêng biệt, nhưng phần javadoc mà bạn đã trích dẫn đầy đủ câu trả lời. Về cơ bản, các trình vòng lặp là an toàn luồng, nhưng nó không được định nghĩa cho dù bạn sẽ thấy tác động của bất kỳ chèn, cập nhật hoặc xóa đồng thời nào được phản ánh trong chuỗi các đối tượng được trả về bởi trình lặp. Trong thực tế, nó có thể phụ thuộc vào nơi trong bản đồ các bản cập nhật xảy ra.

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