4.3.1. Beispiel: Fahrzeugverfolgung durch Delegation
Als ausführlicheres Beispiel für die Delegation wollen wir eine Version des Fahrzeugverfolgers konstruieren, die an eine thread-sichere Klasse delegiert. Wir speichern die Standorte in einer Map, also beginnen wir mit einer thread-sicheren Map-Implementierung, ConcurrentHashMap
. Außerdem speichern wir den Ort mit einer unveränderlichen Point-Klasse anstelle von MutablePoint
wie in Listing 4.6 gezeigt.
Listing 4.6. Unveränderliche Punktklasse, die von DelegatingVehicleTracker verwendet wird.
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
ist thread-sicher, weil es unveränderlich ist. Unveränderliche Werte können frei freigegeben und veröffentlicht werden, so dass wir die Speicherorte bei der Rückgabe nicht mehr kopieren müssen.
DelegatingVehicleTracker
in Listing 4.7 verwendet keine explizite Synchronisierung; der gesamte Zugriff auf den Zustand wird von ConcurrentHashMap
und alle Schlüssel und Werte der Map sind unveränderlich.
Auflistung 4.7. Delegieren der Thread-Sicherheit an eine 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);
}
}
}
Wenn wir das Original verwendet hätten MutablePoint
Klasse anstelle von Point, würden wir die Kapselung brechen, indem wir die getLocations
einen Verweis auf einen veränderlichen Zustand veröffentlichen, der nicht thread-sicher ist. Beachten Sie, dass wir das Verhalten der Fahrzeugverfolgungsklasse geringfügig geändert haben; während die Monitorversion einen Schnappschuss der Standorte zurückgibt, gibt die delegierende Version eine unveränderbare, aber "Live"-Ansicht der Fahrzeugstandorte zurück. Das bedeutet, dass wenn Thread A die getLocations
und Thread B später die Position einiger Punkte ändert, werden diese Änderungen in der an Thread A zurückgegebenen Map berücksichtigt.
4.3.2. Unabhängige Zustandsvariablen
Wir können die Fadensicherheit auch an mehr als eine zugrundeliegende Zustandsvariable delegieren, solange diese zugrundeliegenden Zustandsvariablen unabhängig sind, was bedeutet, dass die zusammengesetzte Klasse keine Invarianten für die mehreren Zustandsvariablen vorschreibt.
VisualComponent
in Listing 4.9 ist eine grafische Komponente, die es den Clients ermöglicht, Listener für Maus- und Tastendruckereignisse zu registrieren. Sie verwaltet eine Liste der registrierten Listener für jeden Typ, so dass beim Auftreten eines Ereignisses die entsprechenden Listener aufgerufen werden können. Es besteht jedoch keine Beziehung zwischen den Maus- und den Tasten-Listenern; die beiden sind unabhängig voneinander, und daher VisualComponent
kann seine Verpflichtungen bezüglich der Thread-Sicherheit an zwei zugrunde liegende Thread-sichere Listen delegieren.
Auflistung 4.9. Delegieren der Thread-Sicherheit an mehrere zugrundeliegende Zustandsvariablen.
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
verwendet eine CopyOnWriteArrayList
um jede Hörerliste zu speichern; dies ist eine thread-sichere List-Implementierung, die sich besonders für die Verwaltung von Hörerlisten eignet (siehe Abschnitt 5.2.3). Jede Liste ist thread-sicher, und da es keine Beschränkungen gibt, die den Zustand der einen mit dem der anderen koppeln, VisualComponent
kann seine Verantwortlichkeiten für die Threadsicherheit an die zugrunde liegende mouseListeners
y keyListeners
objets.
4.3.3. Wenn die Delegation scheitert
Die meisten zusammengesetzten Klassen sind nicht so einfach wie VisualComponent
: Sie haben Invarianten, die ihre Komponenten-Zustandsvariablen in Beziehung setzen. NumberRange
in Listing 4.10 verwendet zwei AtomicIntegers
um seinen Zustand zu verwalten, legt aber eine zusätzliche Bedingung fest, nämlich dass die erste Zahl kleiner oder gleich der zweiten sein muss.
Listing 4.10. Zahlenbereichsklasse, die ihre Invarianten nicht hinreichend schützt. Tun Sie das nicht.
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
es nicht thread-sicher ; sie bewahrt nicht die Invariante, die das untere und obere Ende einschränkt. Die setLower
y setUpper
Methoden versuchen, diese Invariante zu respektieren, tun dies aber nur unzureichend. Beide setLower
y setUpper
sind "check-then-act"-Sequenzen, aber sie verwenden nicht genügend Sperren, um sie atomar zu machen. Wenn der Zahlenbereich (0, 10) gilt, und ein Thread ruft setLower(5)
während ein anderer Thread die setUpper(4)
Mit etwas unglücklichem Timing werden beide die Prüfungen in den Setzern bestehen und beide Änderungen werden angewendet. Das Ergebnis ist, dass der Bereich nun (5, 4)- einen ungültigen Zustand . Also Während die zugrunde liegenden AtomicIntegers thread-sicher sind, ist die zusammengesetzte Klasse nicht thread-sicher. . Da die zugrunde liegenden Zustandsvariablen lower
y upper
sind nicht unabhängig, NumberRange
kann die Thread-Sicherheit nicht einfach an seine Thread-sicheren Zustandsvariablen delegieren.
NumberRange
könnte thread-sicher gemacht werden, indem Sperren verwendet werden, um die Invarianten zu erhalten, wie z.B. das Sichern von lower und upper mit einer gemeinsamen Sperre. Es muss auch vermeiden, lower und upper zu veröffentlichen, um zu verhindern, dass Clients seine Invarianten unterwandern können.
Wenn eine Klasse zusammengesetzte Aktionen hat, wie NumberRange
tut, ist die Delegation allein wiederum kein geeigneter Ansatz für die Fadensicherheit. In diesen Fällen muss die Klasse ihre eigenen Sperren bereitstellen, um sicherzustellen, dass zusammengesetzte Aktionen atomar sind, es sei denn, die gesamte zusammengesetzte Aktion kann auch an die zugrunde liegenden Zustandsvariablen delegiert werden.
Wenn eine Klasse aus mehreren unabhängigen thread-sicheren Zustandsvariablen besteht und keine Operationen mit ungültigen Zustandsübergängen hat, kann sie die Thread-Sicherheit an die zugrunde liegenden Zustandsvariablen delegieren.
5 Stimmen
Normalerweise bedeutet "thread safe", was immer die Person, die den Begriff verwendet, denkt, dass es bedeutet, zumindest für diese Person. Als solches ist es kein sehr nützliches Sprachkonstrukt - man muss viel, viel spezifischer sein, wenn man über das Verhalten von threaded Code spricht.
8 Stimmen
Duplizieren?: stackoverflow.com/questions/261683/
0 Stimmen
@dave Sorry, ich habe versucht zu suchen, aber aufgegeben...trotzdem danke...
1 Stimmen
Ein Code, der nicht auftaucht
Race-Condition