Có một lời giải thích tuyệt vời về vấn đề này bởi Andrei Pangin , ngày 07 tháng 4 năm 2015. Nó có sẵn ở đây , nhưng nó được viết bằng tiếng Nga (tôi khuyên bạn nên xem lại các mẫu mã - chúng là quốc tế). Vấn đề chung là một khóa trong quá trình khởi tạo lớp.
Dưới đây là một số trích dẫn từ bài báo:
Theo JLS , mọi lớp đều có một khóa khởi tạo duy nhất được bắt trong quá trình khởi tạo. Khi luồng khác cố gắng truy cập lớp này trong quá trình khởi tạo, nó sẽ bị chặn trên khóa cho đến khi quá trình khởi tạo hoàn tất. Khi các lớp được khởi tạo đồng thời, có thể xảy ra tình trạng bế tắc.
Tôi đã viết một chương trình đơn giản tính tổng các số nguyên, nó sẽ in ra những gì?
public class StreamSum {
static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();
public static void main(String[] args) {
System.out.println(SUM);
}
}
Bây giờ xóa parallel()
hoặc thay thế lambda bằng Integer::sum
lệnh gọi - điều gì sẽ thay đổi?
Ở đây chúng ta lại thấy deadlock [đã có một số ví dụ về deadlock trong trình khởi tạo lớp trước đây trong bài viết]. Do các parallel()
hoạt động luồng chạy trong một nhóm luồng riêng biệt. Các luồng này cố gắng thực thi lambda body, được viết bằng bytecode như một private static
phương thức bên trong StreamSum
lớp. Nhưng phương thức này không thể được thực thi trước khi hoàn thành trình khởi tạo tĩnh của lớp, phương thức này sẽ đợi kết quả hoàn thành luồng.
Suy nghĩ nhiều hơn là gì: mã này hoạt động khác nhau trong các môi trường khác nhau. Nó sẽ hoạt động chính xác trên một máy CPU và rất có thể sẽ bị treo trên máy nhiều CPU. Sự khác biệt này đến từ việc triển khai nhóm Fork-Join. Bạn có thể tự xác minh việc thay đổi thông số-Djava.util.concurrent.ForkJoinPool.common.parallelism=N