4.3.1. Contoh: Vehicle Tracker Menggunakan Delegasi
Sebagai contoh delegasi yang lebih substansial, mari kita buat versi pelacak kendaraan yang mendelegasikan ke kelas aman-thread. Kami menyimpan lokasi di Peta, jadi kami mulai dengan implementasi Peta yang aman untuk thread ConcurrentHashMap,. Kami juga menyimpan lokasi menggunakan kelas Point yang tidak dapat diubah, bukan seperti yang MutablePointditunjukkan pada Listing 4.6.
Daftar 4.6. Kelas Immutable Point yang digunakan oleh 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;
}
}
Pointaman untuk thread karena tidak dapat diubah. Nilai yang tidak dapat diubah dapat dibagikan dan dipublikasikan dengan bebas, jadi kami tidak perlu lagi menyalin lokasi saat mengembalikannya.
DelegatingVehicleTrackerpada Listing 4.7 tidak menggunakan sinkronisasi eksplisit; semua akses ke status dikelola oleh ConcurrentHashMap, dan semua kunci serta nilai Peta tidak dapat diubah.
Daftar 4.7. Mendelegasikan Keamanan Thread ke 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);
}
}
}
Jika kita menggunakan MutablePointkelas asli dan bukan Point, kita akan merusak enkapsulasi dengan membiarkan getLocationsmempublikasikan referensi ke keadaan yang bisa berubah yang tidak aman untuk thread. Perhatikan bahwa kami telah mengubah sedikit perilaku kelas pelacak kendaraan; sementara versi monitor mengembalikan cuplikan lokasi, versi yang mendelegasikan mengembalikan tampilan lokasi kendaraan yang tidak dapat dimodifikasi tetapi "langsung". Ini berarti bahwa jika utas A memanggil getLocationsdan utas B kemudian mengubah lokasi beberapa titik, perubahan itu tercermin dalam Peta yang dikembalikan ke utas A.
4.3.2. Variabel Status Independen
Kami juga dapat mendelegasikan keamanan thread ke lebih dari satu variabel status yang mendasari selama variabel status yang mendasarinya adalah independen, yang berarti bahwa kelas komposit tidak menerapkan invarian yang melibatkan beberapa variabel status.
VisualComponentpada Listing 4.9 adalah komponen grafis yang memungkinkan klien untuk mendaftarkan listener untuk kejadian tetikus dan penekanan tombol. Ia memelihara daftar pendengar terdaftar dari setiap jenis, sehingga ketika sebuah peristiwa terjadi pendengar yang sesuai dapat dipanggil. Tetapi tidak ada hubungan antara kumpulan pendengar mouse dan pendengar kunci; keduanya independen, dan oleh karena itu VisualComponentdapat mendelegasikan kewajiban keamanan utasnya ke dua daftar utas aman yang mendasarinya.
Daftar 4.9. Mendelegasikan Keamanan Thread ke Beberapa Variabel Status Dasar.
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);
}
}
VisualComponentmenggunakan CopyOnWriteArrayListuntuk menyimpan setiap daftar pendengar; ini adalah implementasi Daftar thread-safe yang sangat cocok untuk mengelola daftar pendengar (lihat Bagian 5.2.3). Setiap Daftar aman untuk thread, dan karena tidak ada batasan yang menggabungkan status satu ke status lainnya, VisualComponentdapat mendelegasikan tanggung jawab keamanan utasnya ke objek mouseListenersdan yang mendasarinya keyListeners.
4.3.3. Saat Delegasi Gagal
Kebanyakan kelas komposit tidak sesederhana VisualComponent: mereka memiliki invarian yang menghubungkan variabel status komponennya. NumberRangepada Listing 4.10 menggunakan dua AtomicIntegersuntuk mengatur statusnya, tetapi memberikan batasan tambahan — bahwa angka pertama kurang dari atau sama dengan yang kedua.
Daftar 4.10. Kelas Rentang Angka yang Tidak Cukup Melindungi Invariannya. Jangan lakukan ini.
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());
}
}
NumberRangeadalah tidak benang-aman ; ia tidak mempertahankan invarian yang membatasi bagian bawah dan atas. Metode setLowerdan setUpperberusaha untuk menghormati invarian ini, tetapi melakukannya dengan buruk. Keduanya setLowerdan setUppermerupakan urutan check-then-act, tetapi tidak menggunakan penguncian yang memadai untuk menjadikannya atomik. Jika rentang nomor menahan (0, 10), dan satu utas memanggil setLower(5)sementara utas lainnya memanggil setUpper(4), dengan beberapa pengaturan waktu yang tidak menguntungkan keduanya akan lulus pemeriksaan di penyetel dan kedua modifikasi akan diterapkan. Hasilnya adalah bahwa kisaran sekarang memegang (5, 4) - status tidak valid . Jadi, meskipun AtomicIntegers yang mendasari aman untuk thread, kelas kompositnya tidak . Karena variabel status yang mendasari lowerdanupper tidak independen,NumberRange tidak bisa begitu saja mendelegasikan keamanan thread ke variabel status aman thread-nya.
NumberRangedapat dibuat thread-safe dengan menggunakan penguncian untuk mempertahankan invariannya, seperti menjaga bagian bawah dan atas dengan kunci umum. Itu juga harus menghindari penerbitan bawah dan atas untuk mencegah klien menumbangkan invariannya.
Jika sebuah kelas memiliki tindakan gabungan, seperti NumberRangehalnya, pendelegasian saja bukanlah pendekatan yang sesuai untuk keamanan thread. Dalam kasus ini, kelas harus menyediakan pengunciannya sendiri untuk memastikan bahwa tindakan gabungan bersifat atomik, kecuali seluruh tindakan gabungan juga dapat didelegasikan ke variabel status yang mendasarinya.
Jika sebuah kelas terdiri dari beberapa variabel status aman thread independen dan tidak memiliki operasi yang memiliki transisi status tidak valid, maka kelas tersebut dapat mendelegasikan keamanan thread ke variabel status yang mendasarinya.