JavaRush /Blog Java /Random-VI /Đồng thời trong Java. Hướng dẫn - Cấu trúc thread-safe.
0xFF
Mức độ
Донецк

Đồng thời trong Java. Hướng dẫn - Cấu trúc thread-safe.

Xuất bản trong nhóm
Sau khi xem xét những rủi ro chính khi chạy các chương trình song song (chẳng hạn như tính nguyên tử hoặc khả năng hiển thị ), chúng ta sẽ xem xét một số thiết kế lớp sẽ giúp chúng ta ngăn chặn những cạm bẫy nêu trên. Một số cấu trúc này tạo ra các đối tượng an toàn cho luồng, cho phép chúng ta chia sẻ chúng một cách an toàn giữa các luồng. Ví dụ, chúng ta sẽ xem xét các đối tượng bất biến và không trạng thái. Các chế độ xem khác sẽ ngăn các luồng khác nhau sửa đổi dữ liệu, chẳng hạn như các biến cục bộ của luồng. Bạn có thể xem tất cả các mã nguồn trên Github . 1. Đối tượng bất biến Đối tượng bất biến có trạng thái (có dữ liệu biểu thị trạng thái của đối tượng), nhưng nó được đặt tại thời điểm tạo trong hàm tạo, khi một thể hiện của đối tượng đã được tạo và trạng thái không thể thay đổi. Mặc dù các luồng có thể luân phiên nhau nhưng một đối tượng vẫn có một trạng thái có thể có. Vì tất cả các trường đều ở chế độ chỉ đọc nên không luồng nào có thể thay đổi dữ liệu của đối tượng. Do đó, một luồng bất biến vốn đã an toàn theo luồng. Lớp Sản phẩm thể hiện một lớp không thể thay đổi. Nó điền vào tất cả các trường của nó trong hàm tạo và không có trường nào thay đổi: public final class Product { private final String id; private final String name; private final double price; public Product(String id, String name, double price) { this.id = id; this.name = name; this.price = price; } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } public boolean equals(Object x) { if (this == x) return true; if (x == null) return false; if (this.getClass() != x.getClass()) return false; Product that = (Product) x; if (!this.id.equals(that.id)) return false; if (!this.name.equals(that.name)) return false; if (this.price != that.price) return false; return true; } public int hashCode() { int hash = 17; hash = 31 * hash + this.getId().hashCode(); hash = 31 * hash + this.getName().hashCode(); hash = 31 * hash + ((Double) this.getPrice()).hashCode(); return hash; } } Trong một số trường hợp, việc đặt các trường cuối cùng sẽ không đủ. Ví dụ: lớp MutableProduct không phải là bất biến, mặc dù tất cả các trường đều là cuối cùng: Tại sao lớp ở trên không bất biến? Lý do là chúng tôi cho phép lấy tham chiếu từ một lớp. Trường ' danh mục ' là một tham chiếu có thể thay đổi, vì vậy sau khi nhận được, khách hàng có thể sửa đổi nó. Để minh họa điều này, hãy xem xét chương trình sau: Và đầu ra của bàn điều khiển: Vì trường 'categories' có thể thay đổi và được lấy từ một đối tượng, nên máy khách đã sửa đổi danh sách này. Một đối tượng lẽ ra không thể thay đổi đã bị thay đổi, dẫn đến một trạng thái mới. Nếu bạn muốn biểu diễn nội dung của một danh sách, bạn có thể sử dụng cách biểu diễn danh sách bất biến: 2. Đối tượng không trạng thái Đối tượng không trạng thái tương tự như đối tượng bất biến, nhưng trong trường hợp này chúng không có trạng thái, thậm chí không có một trạng thái. Khi một đối tượng là một đối tượng không có trạng thái, nó không cần phải lưu giữ bất kỳ dữ liệu nào giữa các lệnh gọi. Vì không có trạng thái nào tồn tại nên không có luồng nào có thể ảnh hưởng đến kết quả của luồng khác bằng cách gọi các phương thức của đối tượng. Vì lý do này, các đối tượng không trạng thái vốn đã an toàn theo luồng. Lớp ProductHandler là một ví dụ về loại đối tượng này. Nó chứa nhiều thao tác trên các đối tượng Sản phẩm và không lưu trữ bất kỳ dữ liệu nào giữa các lệnh gọi. Kết quả của thao tác này không phụ thuộc vào các lệnh gọi trước đó hoặc bất kỳ dữ liệu được lưu trữ nào: Trong phương thức sumCart, ProductHandler chuyển đổi danh sách Sản phẩm thành một mảng để sử dụng trong vòng lặp for-each nhằm lặp qua tất cả các phần tử. Danh sách vòng lặp không an toàn cho luồng và có thể đưa ra Ngoại lệ đồng thờiModificationException nếu có thay đổi trong quá trình lặp. Tùy thuộc vào nhu cầu của bạn, bạn có thể chọn một chiến lược khác . 3. Biến cục bộ của luồng Biến cục bộ của luồng là những biến được xác định trong một luồng. Không có chủ đề nào khác nhìn thấy chúng và sẽ không thay đổi chúng. Loại đầu tiên là biến cục bộ. Trong ví dụ bên dưới, biến Total được lưu trữ trên ngăn xếp của luồng: public final class MutableProduct { private final String id; private final String name; private final double price; private final List categories = new ArrayList<>(); public MutableProduct(String id, String name, double price) { this.id = id; this.name = name; this.price = price; this.categories.add("A"); this.categories.add("B"); this.categories.add("C"); } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public List getCategories() { return this.categories; } public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } } public static void main(String[] args) { MutableProduct p = new MutableProduct("1", "a product", 43.00); System.out.println("Product categories"); for (String c : p.getCategories()) System.out.println(c); p.getCategories().remove(0); System.out.println("\nModified Product categories"); for (String c : p.getCategories()) System.out.println(c); } Product categories A B C Modified Product categories B C public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public class ProductHandler { private static final int DISCOUNT = 90; public Product applyDiscount(Product p) { double finalPrice = p.getPrice() * DISCOUNT / 100; return new Product(p.getId(), p.getName(), finalPrice); } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } Chỉ cần lưu ý rằng nếu bạn xác định một tham chiếu thay vì một biến nguyên thủy và trả về nó, nó sẽ rời khỏi giới hạn của nó. Bạn có thể không biết liên kết trả về được tạo ở đâu. Mã gọi phương thức sumCart có thể lưu trữ nó trong một trường tĩnh và cho phép các luồng khác nhau truy cập vào nó. Loại thứ hai là lớp ThreadLocal . Lớp này cung cấp khả năng lưu trữ độc lập cho mỗi luồng. Các giá trị được lưu trữ trong ThreadLocal có sẵn cho bất kỳ mã nào trên cùng một luồng. Lớp ClientRequestId hiển thị một ví dụ về việc sử dụng lớp ThreadLocale: Lớp ProductHandlerThreadLocal sử dụng ClientRequestId để trả về cùng một ID được tạo trên cùng một luồng: Khi thực thi phương thức chính, đầu ra của bàn điều khiển sẽ hiển thị các ID khác nhau cho mỗi luồng. Ví dụ: Nếu bạn định sử dụng ThreadLocale, bạn phải quan tâm đến một số rủi ro khi sử dụng khi hợp nhất các luồng (như trong các ứng dụng máy chủ). Bạn có thể bị rò rỉ bộ nhớ hoặc rò rỉ thông tin giữa các yêu cầu. Tôi sẽ không đi sâu vào chi tiết về điều này bởi vì... Bài viết “ Cách tự bắn vào chân mình bằng ThreadLocale ” chứng minh rõ điều này có thể xảy ra như thế nào. 4. Sử dụng đồng bộ hóa Một cách khác để cung cấp quyền truy cập an toàn theo luồng tới các đối tượng là thông qua đồng bộ hóa. Nếu chúng ta đồng bộ hóa tất cả quyền truy cập vào một tham chiếu thì chỉ có một đối tượng luồng sẽ truy cập vào nó tại một thời điểm nhất định. Chúng tôi sẽ thảo luận về điều này trong các bài viết trong tương lai. 5. Kết luận Chúng tôi đã xem xét một số phương pháp cho phép bạn xây dựng các đối tượng đơn giản có thể được truy cập bởi nhiều luồng. Việc ngăn ngừa lỗi đa luồng sẽ khó khăn hơn nhiều nếu một đối tượng có thể có nhiều trạng thái. Mặt khác, nếu một đối tượng chỉ có thể có một trạng thái hoặc không có trạng thái nào, chúng ta không phải lo lắng về việc có nhiều luồng truy cập vào đối tượng cùng một lúc. Bản gốc là đây . public class ClientRequestId { private static final ThreadLocal id = new ThreadLocal () { @Override protected String initialValue() { return UUID.randomUUID().toString(); } }; public static String get() { return id.get(); } } public class ProductHandlerThreadLocal { //Same methods as in ProductHandler class public String generateOrderId() { return ClientRequestId.get(); } } T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258 T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e ...
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION