JavaRush /Blog Java /Random-VI /Câu chuyện về hai vòng lặp: Chiến lược sửa đổi cạnh tranh...

Câu chuyện về hai vòng lặp: Chiến lược sửa đổi cạnh tranh trong Java

Xuất bản trong nhóm
Tác giả của ghi chú là Grzegorz Mirek, một nhà phát triển phần mềm đến từ Krakow (Ba Lan). Anh ấy bắt đầu phát triển Java khoảng 6 năm trước, khi vẫn còn học đại học và kể từ đó anh ấy đã không ngừng trau dồi kỹ năng của mình trong lĩnh vực này. Anh ấy đặc biệt quan tâm đến hiệu suất và tối ưu hóa JVM, đây là điều anh ấy chủ yếu viết trên blog của mình .
Câu chuyện về hai vòng lặp: Chiến lược sửa đổi cạnh tranh trong Java - 1
Một số câu hỏi phỏng vấn Java phổ biến nhất bao gồm: Sự khác biệt giữa các trình vòng lặp không nhanh và không an toàn là gì? Câu trả lời đơn giản nhất cho vấn đề này là: Một trình vòng lặp không nhanh sẽ ném ra một ngoại lệ ConcurrentModificationException nếu bộ sưu tập thay đổi trong quá trình lặp, nhưng một trình vòng lặp không an toàn thì không. Mặc dù điều này nghe có vẻ khá có ý nghĩa nhưng vẫn chưa rõ người phỏng vấn có ý gì khi nói không an toàn? Đặc tả ngôn ngữ Java không định nghĩa thuật ngữ này liên quan đến các trình vòng lặp. Tuy nhiên, có bốn chiến lược sửa đổi cạnh tranh.

Sửa đổi cạnh tranh

Trước tiên, hãy xác định sửa đổi cạnh tranh (hoặc song song) là gì. Giả sử chúng ta có một bộ sưu tập và khi trình vòng lặp hoạt động, một số thay đổi xảy ra không đến từ trình vòng lặp này. Trong trường hợp này, chúng tôi nhận được một sửa đổi cạnh tranh. Hãy để tôi cho bạn một ví dụ đơn giản: giả sử chúng ta có một số chủ đề. Luồng đầu tiên lặp lại và luồng thứ hai chèn hoặc xóa các phần tử khỏi cùng một bộ sưu tập. Tuy nhiên, chúng ta có thể gặp ConcurrentModificationException khi chạy trong môi trường đơn luồng:
List<String> cities = new ArrayList<>();
cities.add(Warsaw);
cities.add(Prague);
cities.add(Budapest);

Iterator<String> cityIterator = cities.iterator();
cityIterator.next();
cities.remove(1);
cityIterator.next(); // генерирует ConcurrentModificationException

Thất bại nhanh chóng

Đoạn mã trên là một ví dụ về trình vòng lặp không nhanh . Như bạn có thể thấy, một ngoại lệ ConcurrentModificationException đã bị ném ra khi cố gắng truy xuất phần tử thứ hai từ iterator . Làm cách nào một trình vòng lặp biết rằng bộ sưu tập đã được sửa đổi kể từ khi nó được tạo? Ví dụ: bộ sưu tập có thể có dấu ngày/giờ, chẳng hạn như LastModified . Khi tạo một iterator, bạn nên sao chép trường này và lưu trữ nó trong một đối tượng iterator. Sau đó, mỗi lần bạn gọi phương thức next() , bạn sẽ chỉ so sánh giá trị LastModified từ bộ sưu tập với bản sao từ iterator. Ví dụ, một cách tiếp cận tương tự được sử dụng khi triển khai lớp ArrayList . Nó có một biến thể hiện modCount lưu trữ số lần danh sách đã được sửa đổi:
final void checkForComodification() {
   if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
}
Điều quan trọng cần lưu ý là các trình vòng lặp không nhanh hoạt động trên cơ sở tốt nhất, nghĩa là không có gì đảm bảo rằng ConcurrentModificationException sẽ được đưa ra trong trường hợp có một sửa đổi đồng thời. Vì vậy, bạn không nên dựa vào chúng - thay vào đó, chúng nên được sử dụng để phát hiện lỗi. Hầu hết các bộ sưu tập không đồng thời đều cung cấp các vòng lặp không nhanh .

Tính nhất quán yếu

Hầu hết các bộ sưu tập đồng thời trong gói java.util.concurrent (chẳng hạn như ConcurrentHashMap và hầu hết Queue ) đều cung cấp các trình vòng lặp có tính nhất quán yếu. Ý nghĩa của thuật ngữ này được giải thích rất rõ ràng trong tài liệu :
  • Chúng có thể được xử lý đồng thời với các hoạt động khác
  • Họ không bao giờ ném ConcurrentModificationException
  • Chúng được đảm bảo duyệt qua các phần tử hiện có tại thời điểm trình vòng lặp được tạo chính xác một lần và có thể (nhưng không bắt buộc) phản ánh các sửa đổi tiếp theo.

Ảnh chụp nhanh

Với chiến lược này, trình vòng lặp được liên kết với trạng thái của bộ sưu tập tại thời điểm tạo nó - đây là ảnh chụp nhanh của bộ sưu tập. Bất kỳ thay đổi nào được thực hiện đối với bộ sưu tập ban đầu đều dẫn đến việc tạo ra phiên bản mới của cấu trúc dữ liệu cơ bản. Điều này khiến ảnh chụp nhanh của chúng tôi không thay đổi, vì vậy nó không phản ánh những thay đổi đối với bộ sưu tập xảy ra sau khi trình vòng lặp được tạo. Đây là kỹ thuật copy-on-write (COW) cũ rất tốt . Nó giải quyết hoàn toàn vấn đề sửa đổi đồng thời, do đó, Ngoại lệ ConcurrentModificationException không được tạo ra bằng phương pháp này. Ngoài ra, các trình vòng lặp không hỗ trợ các thao tác thay đổi các phần tử. Các bộ sưu tập sao chép khi ghi có xu hướng quá đắt để sử dụng, nhưng sẽ hợp lý nếu sử dụng chúng nếu các thay đổi xảy ra ít thường xuyên hơn so với việc duyệt qua vòng lặp. Ví dụ là các lớp CopyOnWriteArrayListCopyOnWriteArraySet .

Hành vi không xác định

Bạn có thể gặp phải hành vi không xác định trong các loại bộ sưu tập cũ như VectorHashtable . Cả hai đều có các trình vòng lặp không nhanh tiêu chuẩn , nhưng ngoài ra, chúng còn cho phép sử dụng các triển khai của giao diện Enumeration và chúng không biết cách ứng xử trong trường hợp sửa đổi đồng thời. Bạn có thể gặp phải một số thành phần bị lặp lại hoặc bị thiếu hoặc thậm chí một số trường hợp ngoại lệ lạ. Tốt nhất là đừng chơi với họ!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION