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 MutablePoint
ditunjukkan 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;
}
}
Point
aman 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.
DelegatingVehicleTracker
pada 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 MutablePoint
kelas asli dan bukan Point, kita akan merusak enkapsulasi dengan membiarkan getLocations
mempublikasikan 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 getLocations
dan 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.
VisualComponent
pada 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 VisualComponent
dapat 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);
}
}
VisualComponent
menggunakan CopyOnWriteArrayList
untuk 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, VisualComponent
dapat mendelegasikan tanggung jawab keamanan utasnya ke objek mouseListeners
dan yang mendasarinya keyListeners
.
4.3.3. Saat Delegasi Gagal
Kebanyakan kelas komposit tidak sesederhana VisualComponent
: mereka memiliki invarian yang menghubungkan variabel status komponennya. NumberRange
pada Listing 4.10 menggunakan dua AtomicIntegers
untuk 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());
}
}
NumberRange
adalah tidak benang-aman ; ia tidak mempertahankan invarian yang membatasi bagian bawah dan atas. Metode setLower
dan setUpper
berusaha untuk menghormati invarian ini, tetapi melakukannya dengan buruk. Keduanya setLower
dan setUpper
merupakan 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 lower
danupper
tidak independen,NumberRange
tidak bisa begitu saja mendelegasikan keamanan thread ke variabel status aman thread-nya.
NumberRange
dapat 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 NumberRange
halnya, 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.