4.3.1. Ejemplo: Rastreador de vehículos usando la delegación
Como un ejemplo más sustancial de delegación, construyamos una versión del rastreador de vehículos que delegue en una clase segura para subprocesos. Almacenamos las ubicaciones en un Mapa, por lo que comenzamos con una implementación de Mapa sin hilos, ConcurrentHashMap
. También almacenamos la ubicación utilizando una clase de punto inmutable en lugar de MutablePoint
, que se muestra en el listado 4.6.
Lista 4.6. Clase de punto inmutable utilizada por 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
es seguro para subprocesos porque es inmutable. Los valores inmutables se pueden compartir y publicar libremente, por lo que ya no es necesario copiar las ubicaciones al devolverlos.
DelegatingVehicleTracker
en el listado 4.7 no utiliza ninguna sincronización explícita; todo el acceso al estado está gestionado por ConcurrentHashMap
, y todas las claves y valores del Mapa son inmutables.
Lista 4.7. Delegación de seguridad de subprocesos a una coincidenciaHashMap.
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);
}
}
}
Si se hubiera utilizado la clase original MutablePoint
en lugar de punto, que sería romper la encapsulación dejando getLocations
publicar una referencia al estado mutable que no es seguro para subprocesos. Tenga en cuenta que hemos cambiado ligeramente el comportamiento de la clase de seguimiento de vehículos; mientras que la versión del monitor devolvió una instantánea de las ubicaciones, la versión de delegación devuelve una vista inmodificable pero "en vivo" de las ubicaciones del vehículo. Esto significa que si el hilo A llama a getLocations
y el hilo B modifica posteriormente la ubicación de algunos de los puntos, esos cambios se reflejan en el Mapa devuelto al hilo A.
4.3.2. Variables de estado independientes
También podemos delegar seguridad de subprocesos a más de una variable de estado subyacente siempre que esas variables de estado subyacentes sean independientes, lo que significa que la clase compuesta no impone invariantes que involucren las múltiples variables de estado.
VisualComponent
en el listado 4.9 es un componente gráfico que permite a los clientes registrar escuchas para eventos de mouse y pulsación de teclas. Mantiene una lista de oyentes registrados de cada tipo, de modo que cuando ocurre un evento, se pueden invocar los oyentes apropiados. Pero no existe una relación entre el conjunto de oyentes de ratón y oyentes clave; los dos son independientes y, por lo tanto, VisualComponent
puede delegar sus obligaciones de seguridad de subprocesos a dos listas subyacentes seguras para subprocesos.
Lista 4.9. Delegación de seguridad de subprocesos a múltiples variables de estado subyacentes.
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
utiliza un CopyOnWriteArrayList
para almacenar cada lista de oyentes; esta es una implementación de Lista segura para subprocesos particularmente adecuada para administrar listas de escucha (ver Sección 5.2.3). Cada lista es segura para subprocesos y, como no existen restricciones que acoplen el estado de uno con el estado del otro, VisualComponent
puede delegar sus responsabilidades de seguridad de subprocesos en los objetos subyacentes mouseListeners
y keyListeners
.
4.3.3. Cuando la delegación falla
La mayoría de las clases compuestas no son tan simples como VisualComponent
: tienen invariantes que relacionan las variables de estado de sus componentes. NumberRange
en el listado 4.10 usa dos AtomicIntegers
para administrar su estado, pero impone una restricción adicional: que el primer número sea menor o igual que el segundo.
Lista 4.10. Clase de rango numérico que no protege suficientemente sus invariantes. No hagas esto
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 no seguro para subprocesos; no conserva el invariante que restringe inferior y superior. Los métodos setLower
y setUpper
intentan respetar este invariante, pero lo hacen de manera deficiente. Tanto setLower
como setUpper
son secuencias de verificación y actuación, pero no utilizan el bloqueo suficiente para que sean atómicas. Si el rango de números se mantiene (0, 10), y un hilo llama a setLower(5)
, mientras que otro hilo llama al setUpper(4)
, con un poco de mala sincronización ambos pasarán las verificaciones en los incubadores y se aplicarán ambas modificaciones. El resultado es que el rango ahora tiene (5, 4) - un estado inválido. Así que mientras que los AtomicIntegers subyacentes son seguros para subprocesos, la clase compuesta no es. Debido a que las variables de estado subyacentes lower
y upper
no son independientes, NumberRange
no puede simplemente delegar seguridad de subprocesos a sus variables de estado de subprocesos.
NumberRange
podría hacerse a prueba de roscas mediante el bloqueo para mantener sus invariantes, como la protección de la parte inferior y superior con un bloqueo común. También debe evitar publicar más bajo y más alto para evitar que los clientes subviertan sus invariantes.
Si una clase tiene acciones compuestas, como NumberRange
, la delegación por sí sola tampoco es un enfoque adecuado para la seguridad de subprocesos. En estos casos, la clase debe proporcionar su propio bloqueo para garantizar que las acciones compuestas sean atómicas, a menos que toda la acción compuesta también pueda delegarse a las variables de estado subyacentes.
Si una clase se compone de múltiples variables de estado independientes de subprocesos y no tiene operaciones que tengan transiciones de estado no válidas, puede delegar seguridad de subprocesos a las variables de estado subyacentes.
por "eso" me refiero a una excepción –
Por lo general, "hilo de seguridad" significa cualquiera que sea la persona que utiliza el término piensa que significa, al menos a esa persona. Como tal, no es una construcción de lenguaje muy útil; necesitas ser mucho, mucho más específico cuando hablas del comportamiento del código enhebrado. –
¿Duplicado ?: http: // stackoverflow.com/questions/261683/what-is-meant-by-thread-safe-code –