- Trước đây, mức sử dụng CPU trên hệ thống của tôi đã lên tới 3.200%, tức là cả 32 lõi đều bị lấp đầy
- Tôi đang dùng runtime Java 17, và khi kiểm tra thời gian CPU trong thread dump rồi sắp xếp theo thời gian CPU, tôi phát hiện nhiều thread tương tự nhau
- Phân tích đoạn mã gây ra vấn đề
- Thông qua stack trace, tôi xác định được dòng 29 trong lớp
BusinessLogic
- Đoạn mã đó có dạng lặp qua danh sách
unrelatedObjects rồi chèn giá trị của relatedObject vào treeMap
- Đây là đoạn mã không hiệu quả vì không sử dụng
unrelatedObject bên trong vòng lặp
Sửa mã và kiểm thử
- Loại bỏ vòng lặp không cần thiết và sửa thành một dòng
treeMap.put(relatedObject.a(), relatedObject.b());
- Tôi đã chạy unit test trước và sau khi sửa, nhưng không thể tái hiện vấn đề
- Ngay cả khi kích thước của
treeMap và unrelatedObjects đều vượt quá 1.000.000 phần tử, vấn đề vẫn không xảy ra
Phát hiện nguyên nhân của vấn đề
treeMap đang bị nhiều thread truy cập đồng thời và không hề được đồng bộ hóa
- Đây là vấn đề phát sinh khi nhiều thread cùng lúc sửa đổi
TreeMap
Tái hiện vấn đề bằng thực nghiệm
- Tôi tiến hành một thử nghiệm trong đó nhiều thread cập nhật ngẫu nhiên vào
TreeMap dùng chung
- Thiết lập
try-catch để bỏ qua NullPointerException
- Kết quả thử nghiệm cho thấy mức sử dụng CPU tăng vọt lên tới 500%
Kết luận
- Việc sửa đổi đồng thời một
TreeMap không được đồng bộ hóa có thể gây ra vấn đề hiệu năng nghiêm trọng
- Để tránh vấn đề này, nên đồng bộ hóa
TreeMap hoặc sử dụng collection an toàn luồng như ConcurrentMap
1 bình luận
Ý kiến Hacker News
Tôi từng nghĩ race condition sẽ gây hỏng dữ liệu hoặc deadlock, nhưng chưa nghĩ rằng nó cũng có thể gây ra vấn đề hiệu năng. Dữ liệu có thể bị hỏng theo cách tạo ra vòng lặp vô hạn
Trong code có nhiều thread hoạt động, chiến lược chắc chắn duy nhất là làm cho mọi đối tượng trở nên bất biến, còn những đối tượng không thể bất biến thì phải được giới hạn trong các phần nhỏ, tự khép kín và được kiểm soát nghiêm ngặt
Câu "gần như không thể ssh vào" khiến tôi nhớ đến thời học cao học dùng Sun UltraSparc 170
Có thể đơn giản rút gọn code thành như sau
Một cách khác để tạo ra vòng lặp vô hạn là dùng triển khai <i>Comparator</i> hoặc <i>Comparable</i> không thực hiện một thứ tự toàn phần nhất quán
Có thể cân nhắc dùng bộ đếm tăng dần để phát hiện chu kỳ, và ném ngoại lệ nếu vượt quá độ sâu của cây hoặc kích thước collection
Trong Java, thực hiện thao tác đồng thời trên các đối tượng không thread-safe tạo ra những bug thú vị nhất
Có câu hỏi liệu một TreeMap không được bảo vệ có thể gây ra mức sử dụng 3,200% hay không
Tác giả đã phát hiện ra một dạng Poison Pill. Điều này phổ biến hơn trong các hệ thống event sourcing, nơi một thông điệp giết chết mọi thứ mà nó chạm vào
Ngoại lệ trong thread là một vấn đề cực lớn