4.3.1. Ví dụ: Trình theo dõi xe sử dụng ủy quyền
Như một ví dụ quan trọng hơn về ủy quyền, hãy xây dựng một phiên bản của trình theo dõi phương tiện ủy quyền cho một lớp an toàn theo luồng. Chúng tôi lưu trữ các vị trí trong Bản đồ, vì vậy chúng tôi bắt đầu với việc triển khai Bản đồ an toàn theo chuỗi ConcurrentHashMap
,. Chúng tôi cũng lưu trữ vị trí bằng cách sử dụng lớp Điểm bất biến thay vì MutablePoint
, được hiển thị trong Liệt kê 4.6.
Liệt kê 4.6. Lớp Immutable Point được sử dụng bởi DelegateVehicleTracker.
class Point{
public final int x, y;
public Point() {
this.x=0; this.y=0;
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point
là luồng an toàn vì nó là bất biến. Các giá trị bất biến có thể được chia sẻ và xuất bản miễn phí, vì vậy chúng tôi không cần phải sao chép các vị trí khi trả lại chúng.
DelegatingVehicleTracker
trong Liệt kê 4.7 không sử dụng bất kỳ đồng bộ hóa rõ ràng nào; tất cả quyền truy cập vào trạng thái được quản lý bởi ConcurrentHashMap
và tất cả các khóa và giá trị của Bản đồ là bất biến.
Liệt kê 4.7. Ủy quyền an toàn chuỗi cho một ConcurrentHashMap.
public class DelegatingVehicleTracker {
private final ConcurrentMap<String, Point> locations;
private final Map<String, Point> unmodifiableMap;
public DelegatingVehicleTracker(Map<String, Point> points) {
this.locations = new ConcurrentHashMap<String, Point>(points);
this.unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String, Point> getLocations(){
return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
}
public Point getLocation(String id) {
return locations.get(id);
}
public void setLocation(String id, int x, int y) {
if(locations.replace(id, new Point(x, y)) == null) {
throw new IllegalArgumentException("invalid vehicle name: " + id);
}
}
}
Nếu chúng ta đã sử dụng MutablePoint
lớp ban đầu thay vì Point, chúng ta sẽ phá vỡ tính đóng gói bằng cách cho phép getLocations
xuất bản một tham chiếu đến trạng thái có thể thay đổi không an toàn cho luồng. Lưu ý rằng chúng tôi đã thay đổi hành vi của lớp theo dõi xe một chút; trong khi phiên bản giám sát trả về ảnh chụp nhanh các vị trí, phiên bản ủy quyền trả về chế độ xem không thể thay đổi nhưng “trực tiếp” về các vị trí xe. Điều này có nghĩa là nếu luồng A gọi getLocations
và luồng B sau đó sửa đổi vị trí của một số điểm, những thay đổi đó sẽ được phản ánh trong Bản đồ được trả về luồng A.
4.3.2. Các biến trạng thái độc lập
Chúng ta cũng có thể ủy quyền an toàn luồng cho nhiều hơn một biến trạng thái cơ bản miễn là các biến trạng thái cơ bản đó độc lập, nghĩa là lớp tổng hợp không áp đặt bất kỳ biến trạng thái nào liên quan đến nhiều biến trạng thái.
VisualComponent
trong Liệt kê 4.9 là một thành phần đồ họa cho phép máy khách đăng ký bộ lắng nghe cho các sự kiện chuột và tổ hợp phím. Nó duy trì một danh sách các trình nghe đã đăng ký của từng loại, để khi một sự kiện xảy ra, các trình nghe thích hợp có thể được gọi ra. Nhưng không có mối quan hệ nào giữa tập hợp những người nghe chuột và những người nghe chính; hai là độc lập và do đó VisualComponent
có thể ủy thác các nghĩa vụ an toàn luồng của nó cho hai danh sách an toàn luồng cơ bản.
Liệt kê 4.9. Ủy quyền an toàn chuỗi cho nhiều biến trạng thái cơ bản.
public class VisualComponent {
private final List<KeyListener> keyListeners
= new CopyOnWriteArrayList<KeyListener>();
private final List<MouseListener> mouseListeners
= new CopyOnWriteArrayList<MouseListener>();
public void addKeyListener(KeyListener listener) {
keyListeners.add(listener);
}
public void addMouseListener(MouseListener listener) {
mouseListeners.add(listener);
}
public void removeKeyListener(KeyListener listener) {
keyListeners.remove(listener);
}
public void removeMouseListener(MouseListener listener) {
mouseListeners.remove(listener);
}
}
VisualComponent
sử dụng a CopyOnWriteArrayList
để lưu trữ từng danh sách người nghe; đây là cách triển khai Danh sách an toàn theo luồng đặc biệt thích hợp để quản lý danh sách người nghe (xem Phần 5.2.3). Mỗi Danh sách là an toàn luồng và bởi vì không có ràng buộc nào kết hợp trạng thái của một cái với trạng thái của cái kia, VisualComponent
có thể ủy thác trách nhiệm an toàn luồng của nó cho các đối tượng mouseListeners
và keyListeners
đối tượng bên dưới .
4.3.3. Khi ủy quyền thất bại
Hầu hết các lớp hỗn hợp không đơn giản như VisualComponent
: chúng có các biến bất biến liên quan đến các biến trạng thái thành phần của chúng. NumberRange
trong Liệt kê 4.10 sử dụng hai AtomicIntegers
để quản lý trạng thái của nó, nhưng áp đặt một ràng buộc bổ sung — rằng số đầu tiên nhỏ hơn hoặc bằng số thứ hai.
Liệt kê 4.10. Lớp Dãy số không đủ bảo vệ những kẻ bất biến của nó. Đừng làm điều này.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
//Warning - unsafe check-then-act
if(i > upper.get()) {
throw new IllegalArgumentException(
"Can't set lower to " + i + " > upper ");
}
lower.set(i);
}
public void setUpper(int i) {
//Warning - unsafe check-then-act
if(i < lower.get()) {
throw new IllegalArgumentException(
"Can't set upper to " + i + " < lower ");
}
upper.set(i);
}
public boolean isInRange(int i){
return (i >= lower.get() && i <= upper.get());
}
}
NumberRange
là không thread-safe ; nó không bảo toàn bất biến mà ràng buộc thấp hơn và trên. Các setLower
và setUpper
phương pháp cố gắng tôn trọng sự bất biến này, nhưng làm như vậy kém. Cả hai setLower
và setUpper
đều là trình tự kiểm tra sau đó hành động, nhưng chúng không sử dụng đủ khóa để biến chúng thành nguyên tử. Nếu phạm vi số giữ (0, 10) và một luồng gọi setLower(5)
trong khi luồng khác gọi setUpper(4)
, với một số thời gian không may mắn, cả hai sẽ vượt qua các kiểm tra trong bộ cài đặt và cả hai sửa đổi sẽ được áp dụng. Kết quả là phạm vi hiện giữ (5, 4) - trạng thái không hợp lệ . Vì vậy, trong khi các AtomicIntegers cơ bản là an toàn cho luồng, thì lớp hỗn hợp thì không . Bởi vì các biến trạng thái cơ bản lower
vàupper
không độc lập, NumberRange
không thể chỉ ủy quyền an toàn luồng cho các biến trạng thái an toàn luồng của nó.
NumberRange
có thể được thực hiện an toàn theo luồng bằng cách sử dụng khóa để duy trì các bất biến của nó, chẳng hạn như bảo vệ phía dưới và phía trên bằng một khóa chung. Nó cũng phải tránh xuất bản thấp hơn và cao hơn để ngăn khách hàng lật đổ những gì bất biến của nó.
Nếu một lớp có các hành động phức hợp, NumberRange
thì việc ủy quyền một lần nữa không phải là cách tiếp cận phù hợp cho an toàn luồng. Trong những trường hợp này, lớp phải cung cấp khóa riêng của nó để đảm bảo rằng các hành động phức hợp là nguyên tử, trừ khi toàn bộ hành động ghép cũng có thể được ủy quyền cho các biến trạng thái cơ bản.
Nếu một lớp bao gồm nhiều biến trạng thái an toàn luồng độc lập và không có hoạt động nào có bất kỳ chuyển đổi trạng thái không hợp lệ nào, thì nó có thể ủy quyền an toàn luồng cho các biến trạng thái cơ bản.