2017-05-28 14 views
8

Javadoc từ ConcurrentHashMap#computeIfAbsent nóiHậu quả của việc cập nhật quan trọng khác (s) trong ConcurrentHashMap # computeIfAbsent

Các tính toán nên ngắn gọn và đơn giản, và không phải cố gắng để cập nhật bất kỳ ánh xạ khác của bản đồ này.

Nhưng, từ những gì tôi nhìn thấy, sử dụng remove()clear() phương pháp bên mappingFunction hoạt động tốt. Ví dụ này

Key element = elements.computeIfAbsent(key, e -> { 
    if (usages.size() == maxSize) { 
     elements.remove(oldest); 
    } 
    return loader.load(key); 
}); 

gì hậu quả của việc sử dụng loại bỏ xấu() phương pháp bên mappingFunction có thể được?

+0

Thông số kỹ thuật như vậy nên được đọc rất cẩn thận. Các từ "không được cố gắng" chỉ ra rằng bạn có thể phá vỡ một cái gì đó ở đâu đó nếu bạn không tuân theo quy tắc. Nhưng khi so sánh với Andy tôi luôn thực sự quan tâm đến lý do tại sao những hạn chế như vậy được đưa ra. –

Trả lời

4

Các javadoc giải thích rõ nguyên nhân:

Một số thao tác cập nhật đã cố gắng trên bản đồ này bằng cách đề khác có thể chặn trong khi tính toán là cơ bản dở dang, vì vậy việc tính toán nên ngắn và đơn giản và không được cố gắng cập nhật bất kỳ ánh xạ nào khác của bản đồ này.

Bạn có không quên rằng ConcurrentHashMap được thiết kế để cung cấp một cách để sử dụng một sợi Bản đồ an toàn mà không bị như khóa vì nó là trường hợp cho chủ đề cũ lớp Bản đồ an toàn như HashTable.
Khi sửa đổi trên bản đồ xảy ra, nó chỉ khóa bản đồ liên quan chứ không phải toàn bộ bản đồ.

ConcurrentHashMap là một bảng băm hỗ trợ đồng thời đầy đủ các năng tìm lại và đồng thời dự kiến ​​cao để cập nhật.

computeIfAbsent() là một phương pháp mới được bổ sung trong Java 8.
Nếu sử dụng nặng, có nghĩa là, nếu trong cơ thể của computeIfAbsent() rằng đã khóa lập bản đồ của khóa thông qua với phương pháp này, bạn khóa phím khác, bạn nhập vào một con đường mà bạn có thể đánh bại mục đích của ConcurrentHashMap là cuối cùng bạn sẽ khóa hai ánh xạ.
Hãy tưởng tượng vấn đề nếu bạn khóa nhiều ánh xạ hơn bên trong computeIfAbsent() và phương thức đó không hề ngắn chút nào. Truy cập đồng thời trên bản đồ sẽ trở nên chậm chạp.

Vì vậy, javadoc của computeIfAbsent() nhấn mạnh về vấn đề tiềm ẩn này bằng cách nhắc lại nguyên tắc ConcurrentHashMap: giữ cho nó đơn giản và nhanh chóng.


Đây là mã ví dụ minh họa sự cố.
Giả sử chúng tôi có một phiên bản ConcurrentHashMap<Integer, String>.

Chúng tôi sẽ bắt đầu hai chủ đề mà sử dụng nó:

  • Các chủ đề đầu tiên: thread1 mà gọi computeIfAbsent() với phím 1
  • Các chủ đề thứ hai: thread2 mà gọi computeIfAbsent() với phím 2

thread1 thực thi tác vụ đủ nhanh nhưng không tuân theo lời khuyên của computeIfAbsent() javadoc: nó cập nhật khóa 2 trong computeIfAbsent(), đó là một ánh xạ khác được sử dụng trong ngữ cảnh hiện tại của phương thức (đó là khóa 1).

thread2 thực hiện tác vụ đủ dài. Nó gọi computeIfAbsent() với khóa 2 bằng cách làm theo các lời khuyên của javadoc: nó không cập nhật bất kỳ ánh xạ nào khác trong việc thực hiện nó.
Để mô phỏng tác vụ dài, chúng tôi có thể sử dụng phương pháp Thread.sleep() với thông số 5000.

Đối với tình hình cụ thể này, nếu thread2 bắt đầu trước khi thread1, invocation của map.put(2, someValue); trong thread1 sẽ bị chặn trong khi thread2 không được trả lại của computeIfAbsent() mà khóa các bản đồ của khóa 2.

Cuối cùng, chúng tôi nhận một trường hợp ConcurrentHashMap chặn các bản đồ của khóa 2 trong 5 giây khi computeIfAbsent() được gọi với các bản đồ của khóa 1.
Đó là sai lầm, không có hiệu quả và nó đi ngược lại các ConcurrentHashMap cố ý và hậu computeIfAbsent() mô tả mà mục đích được tính toán giá trị cho khóa hiện tại:

nếu phím chỉ định không được kết hợp với một giá trị, nỗ lực để tính toán giá trị của nó bằng cách sử dụng chức năng lập bản đồ nhất định và đi vào nó vào bản đồ này trừ khi vô

mẫu mã:

import java.util.concurrent.ConcurrentHashMap; 

public class BlockingCallOfComputeIfAbsentWithConcurrentHashMap { 

    public static void main(String[] args) throws InterruptedException { 
    ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>(); 

    Thread thread1 = new Thread() { 
     @Override 
     public void run() { 
      map.computeIfAbsent(1, e -> { 
       String valueForKey2 = map.get(2); 
       System.out.println("thread1 : get() returns with value for key 2 = " + valueForKey2); 
       String oldValueForKey2 = map.put(2, "newValue"); 
       System.out.println("thread1 : after put() returns, previous value for key 2 = " + oldValueForKey2); 
       return map.get(2); 
      }); 
     } 
    }; 

    Thread thread2 = new Thread() { 
     @Override 
     public void run() { 
      map.computeIfAbsent(2, e -> { 
      try { 
       Thread.sleep(5000); 
      } catch (Exception e1) { 
       e1.printStackTrace(); 
      } 
      String value = "valueSetByThread2"; 
      System.out.println("thread2 : computeIfAbsent() returns with value for key 2 = " + value); 
      return value; 
      }); 
     } 
    }; 

    thread2.start(); 
    Thread.sleep(1000); 
    thread1.start(); 
    } 
} 

Khi sản lượng chúng tôi luôn nhận được:

thread1: get() trả về với giá trị cho khóa 2 = null

thread2: computeIfAbsent() trả về với giá trị cho khóa 2 = valueSetByThread2

thread1: after put() trả về, giá trị trước đó cho khóa 2 = valueSetByThread2

Đây là writt en nhanh như đọc trên ConcurrentHashMap không chặn:

thread1: get() trả về với giá trị cho khóa 2 = null

nhưng điều này:

thread1: sau khi đặt () trả về, giá trị trước đó cho khóa 2 = giá trịSetByThread2

chỉ xuất khi thread2 được trả về của computeIfAbsent().

5

Dưới đây là một ví dụ về một hậu quả xấu:

ConcurrentHashMap<Integer,String> cmap = new ConcurrentHashMap<>(); 
cmap.computeIfAbsent (1, e-> {cmap.remove (1); return "x";}); 

Mã này gây ra bế tắc.

2

Lời khuyên như vậy hơi giống như lời khuyên không đi xuống giữa một con đường. Bạn có thể làm điều đó, và bạn cũng có thể không bị xe đụng; bạn cũng có thể thoát ra khỏi đường nếu bạn thấy chiếc xe đến.

Tuy nhiên, bạn sẽ an toàn hơn nếu bạn chỉ ở trên vỉa hè (vỉa hè) ngay từ đầu.

Nếu tài liệu API yêu cầu bạn không làm điều gì đó, tất nhiên không có gì ngăn bạn làm việc đó. Và bạn có thể thử làm điều đó, và thấy rằng không có hậu quả xấu, ít nhất là trong những hoàn cảnh hạn chế mà bạn thử nghiệm. Bạn thậm chí có thể đào sâu để tìm hiểu lý do chính xác tại sao lời khuyên có đó; bạn có thể xem xét kỹ mã nguồn và chứng minh rằng nó an toàn trong trường hợp sử dụng của bạn.

Tuy nhiên, những người triển khai API được tự do thay đổi việc triển khai, trong các ràng buộc của hợp đồng được mô tả bằng tài liệu API. Họ có thể thực hiện thay đổi ngừng mã của bạn vào ngày mai vì họ không có nghĩa vụ bảo vệ hành vi mà họ cảnh báo rõ ràng khi sử dụng.

Vì vậy, để trả lời câu hỏi của bạn về những hậu quả xấu có thể là gì: nghĩa là bất cứ điều gì (tốt, bất cứ điều gì hoàn thành bình thường hoặc ném một RuntimeException); và bạn sẽ không nhất thiết phải quan sát các hậu quả tương tự theo thời gian hoặc trên các JVM khác nhau.

Ở trên vỉa hè: không làm những gì tài liệu cho bạn biết.

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