4.3.1 ตัวอย่าง: Vehicle Tracker โดยใช้ Delegation
เพื่อเป็นตัวอย่างที่ชัดเจนยิ่งขึ้นของการมอบหมายให้สร้างเวอร์ชันของตัวติดตามยานพาหนะที่มอบสิทธิ์ให้กับคลาสเธรดที่ปลอดภัย ConcurrentHashMap
เราจัดเก็บสถานที่ในแผนที่ดังนั้นเราจึงเริ่มต้นด้วยด้ายปลอดภัยแผนที่การดำเนินงาน นอกจากนี้เรายังจัดเก็บตำแหน่งโดยใช้คลาส Point ที่ไม่เปลี่ยนรูปแทนMutablePoint
ซึ่งแสดงไว้ในรายการ 4.6
รายการ 4.6. คลาสจุดไม่เปลี่ยนรูปที่ใช้โดย DelegatingVehicleTracker
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
ปลอดภัยต่อเธรดเนื่องจากไม่เปลี่ยนรูป คุณสามารถแบ่งปันและเผยแพร่ค่าที่ไม่เปลี่ยนรูปได้อย่างอิสระดังนั้นเราจึงไม่จำเป็นต้องคัดลอกสถานที่อีกต่อไปเมื่อส่งคืน
DelegatingVehicleTracker
ในรายการ 4.7 ไม่ได้ใช้การซิงโครไนซ์ที่ชัดเจนใด ๆ การเข้าถึงสถานะทั้งหมดได้รับการจัดการConcurrentHashMap
และคีย์และค่าทั้งหมดของแผนที่ไม่เปลี่ยนรูป
รายการ 4.7. การมอบหมายความปลอดภัยของเธรดให้กับ 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);
}
}
}
ถ้าเราใช้MutablePoint
คลาสดั้งเดิมแทน Point เราจะทำลายการห่อหุ้มโดยปล่อยgetLocations
การอ้างอิงไปยังสถานะที่เปลี่ยนแปลงได้ซึ่งไม่ปลอดภัยต่อเธรด สังเกตว่าเราได้เปลี่ยนพฤติกรรมของคลาสติดตามยานพาหนะเล็กน้อย ในขณะที่เวอร์ชันมอนิเตอร์ส่งคืนสแน็ปช็อตของตำแหน่งเวอร์ชันที่มอบสิทธิ์จะส่งคืนมุมมองที่ไม่สามารถปรับเปลี่ยนได้ แต่เป็น "สด" ของตำแหน่งรถ ซึ่งหมายความว่าหากเธรด A เรียกgetLocations
และเธรด B แก้ไขตำแหน่งของจุดบางจุดในภายหลังการเปลี่ยนแปลงเหล่านั้นจะแสดงในแผนที่กลับไปยังเธรด A
4.3.2 ตัวแปรสถานะอิสระ
นอกจากนี้เรายังสามารถมอบหมายความปลอดภัยของเธรดให้กับตัวแปรสถานะพื้นฐานได้มากกว่าหนึ่งตัวแปรตราบเท่าที่ตัวแปรสถานะพื้นฐานเหล่านั้นเป็นอิสระซึ่งหมายความว่าคลาสคอมโพสิตไม่ได้กำหนดค่าคงที่ที่เกี่ยวข้องกับตัวแปรหลายสถานะ
VisualComponent
ในรายการ 4.9 เป็นส่วนประกอบกราฟิกที่ช่วยให้ไคลเอนต์สามารถลงทะเบียนผู้ฟังสำหรับเหตุการณ์เมาส์และการกดแป้นพิมพ์ จะเก็บรายชื่อผู้ฟังที่ลงทะเบียนไว้ในแต่ละประเภทเพื่อให้เมื่อเกิดเหตุการณ์ขึ้นจะสามารถเรียกผู้ฟังที่เหมาะสมได้ แต่ไม่มีความสัมพันธ์ระหว่างชุดของตัวฟังเมาส์และตัวฟังหลัก ทั้งสองเป็นอิสระดังนั้นจึงVisualComponent
สามารถมอบหมายภาระหน้าที่ด้านความปลอดภัยของเธรดให้กับรายการเธรดที่ปลอดภัยสองรายการ
รายการ 4.9. การมอบหมายความปลอดภัยของเธรดให้กับตัวแปรสถานะพื้นฐานหลายตัวแปร
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
ใช้CopyOnWriteArrayList
เพื่อจัดเก็บรายชื่อผู้ฟังแต่ละคน นี่คือการใช้งานรายการที่ปลอดภัยสำหรับเธรดที่เหมาะอย่างยิ่งสำหรับการจัดการรายการผู้ฟัง (ดูหัวข้อ 5.2.3) แต่ละรายการมีความปลอดภัยต่อเธรดและเนื่องจากไม่มีข้อ จำกัด ในการเชื่อมต่อสถานะของหนึ่งกับสถานะของอีกสถานะหนึ่งจึงVisualComponent
สามารถมอบหมายความรับผิดชอบด้านความปลอดภัยของเธรดให้กับสิ่งที่อยู่ภายใต้mouseListeners
และkeyListeners
วัตถุได้
4.3.3 เมื่อการมอบสิทธิ์ล้มเหลว
คลาสคอมโพสิตส่วนใหญ่ไม่ง่ายอย่างที่VisualComponent
: พวกเขามีค่าคงที่ที่เกี่ยวข้องกับตัวแปรสถานะองค์ประกอบของพวกเขา NumberRange
ในรายการ 4.10 ใช้สองAtomicIntegers
เพื่อจัดการสถานะของมัน แต่กำหนดข้อ จำกัด เพิ่มเติมนั่นคือตัวเลขแรกน้อยกว่าหรือเท่ากับตัวที่สอง
รายการ 4.10. คลาสช่วงจำนวนที่ไม่สามารถป้องกันค่าคงที่ได้อย่างเพียงพอ อย่าทำแบบนี้
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
คือไม่ด้ายปลอดภัย ; มันไม่ได้รักษาค่าคงที่ที่ จำกัด ด้านล่างและด้านบน setLower
และsetUpper
วิธีการพยายามที่จะเคารพคงที่นี้ แต่ทำได้ไม่ดี ทั้งสองsetLower
และsetUpper
เป็นลำดับที่ตรวจสอบแล้วดำเนินการ แต่ไม่ได้ใช้การล็อกที่เพียงพอที่จะทำให้เป็นอะตอม หากช่วงตัวเลขมีค่า (0, 10) และมีการเรียกเธรดหนึ่งsetLower(5)
ในขณะที่อีกเธรดหนึ่งเรียกsetUpper(4)
ด้วยเวลาที่โชคร้ายทั้งสองจะผ่านการตรวจสอบในตัวตั้งค่าและการแก้ไขทั้งสองจะถูกนำไปใช้ ผลที่ได้คือว่าช่วงนี้ถือ (5, 4) - เป็นรัฐที่ไม่ถูกต้อง ดังนั้นในขณะที่ AtomicIntegers ต้นแบบด้ายปลอดภัยชั้นคอมโพสิตไม่ เนื่องจากตัวแปรสถานะพื้นฐานlower
และupper
ไม่เป็นอิสระไม่NumberRange
สามารถมอบหมายความปลอดภัยของเธรดให้กับตัวแปรสถานะเธรดที่ปลอดภัยได้
NumberRange
สามารถทำให้ปลอดภัยต่อเกลียวได้โดยใช้การล็อคเพื่อรักษาค่าคงที่เช่นการป้องกันด้านล่างและด้านบนด้วยล็อคทั่วไป นอกจากนี้ยังต้องหลีกเลี่ยงการเผยแพร่ด้านล่างและด้านบนเพื่อป้องกันไม่ให้ลูกค้าล้มล้างค่าคงที่
หากคลาสมีการดำเนินการแบบผสมเช่นเดียวกับการNumberRange
มอบหมายเพียงอย่างเดียวก็ไม่ใช่แนวทางที่เหมาะสมสำหรับความปลอดภัยของเธรด ในกรณีเหล่านี้คลาสจะต้องจัดเตรียมการล็อคของตัวเองเพื่อให้แน่ใจว่าการกระทำแบบผสมเป็นอะตอมเว้นแต่จะสามารถมอบหมายการกระทำแบบผสมทั้งหมดให้กับตัวแปรสถานะพื้นฐาน
หากคลาสประกอบด้วยตัวแปรสถานะที่ปลอดภัยเธรดอิสระหลายตัวและไม่มีการดำเนินการที่มีการเปลี่ยนสถานะที่ไม่ถูกต้องใด ๆ ก็สามารถมอบหมายความปลอดภัยของเธรดให้กับตัวแปรสถานะที่อยู่ภายใต้