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()
.
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. –