java - example - Multi-threading: objetos que se establecen en null mientras los usan
java concurrency (3)
Tengo una pequeña aplicación que tiene un hilo de renderizado. Todo lo que hace este hilo es dibujar mis objetos en su ubicación actual.
Tengo un código como:
public void render()
{
// ... rendering various objects
if (mouseBall != null) mouseBall.draw()
}
Luego, también tengo un controlador de mouse que crea y establece mouseBall a una nueva bola cuando el usuario hace clic en el mouse. El usuario puede arrastrar el mouse y la bola seguirá donde va el mouse. Cuando el usuario suelta la pelota, tengo otro evento de mouse que establece mouseBall = null.
El problema es que mi ciclo de renderización se está ejecutando lo suficientemente rápido como para que al azar el condicional (mouseBall! = Null) devuelva verdadero, pero en esa fracción de segundo después de ese punto el usuario soltará el mouse y obtendré un nullpointer excepción para intentar .draw () en un objeto nulo.
¿Cuál es la solución a un problema como este?
Debe sincronizar las instrucciones if y draw para garantizar que se ejecuten como una secuencia atómica. En Java, esto se haría así:
public void render()
{
// ... rendering various objects
synchronized(this) {
if (mouseBall != null) mouseBall .draw();
}
}
El problema radica en el hecho de que está accediendo a mouseBall
dos veces, una para verificar si no es null
y otra para llamar a una función. Puede evitar este problema usando un temporal como este:
public void render()
{
// ... rendering various objects
tmpBall = mouseBall;
if (tmpBall != null) tmpBall.draw();
}
Sé que ya has aceptado otras respuestas, pero una tercera opción sería utilizar la clase AtomicReference del paquete java.util.concurrent.atomic. Esto proporciona operaciones de recuperación, actualización y comparación que actúan atómicamente sin que necesites ningún código de soporte. Entonces en tu ejemplo:
public void render()
{
AtomicReference<MouseBallClass> mouseBall = ...;
// ... rendering various objects
MouseBall tmpBall = mouseBall.get();
if (tmpBall != null) tmpBall.draw();
}
Esto se ve muy similar a la solución de Greg, y conceptualmente son similares en que detrás de las escenas ambos usan la volatilidad para garantizar la frescura de los valores, y toman una copia temporal para aplicar un condicional antes de usar el valor.
En consecuencia, el ejemplo exacto utilizado aquí no es tan bueno para mostrar el poder de las AtomicReferences. Considere en cambio que su otro hilo actualizará el mouseball solo si ya era nulo, una expresión útil para varios bloques de código de estilo de inicialización. En este caso, normalmente sería esencial usar la sincronización, para garantizar que si marcabas y encontrabas que la bola era nula, aún sería nulo cuando intentaste establecerlo (de lo contrario estarás en el reino de tu problema original) ) Sin embargo, con AtomicReference puedes decir simplemente:
mouseBall.compareAndSet(null, possibleNewBall);
porque esta es una operación atómica, por lo que si un hilo "ve" el valor como nulo, también lo establecerá en la referencia de posible nueva bola antes de que otros hilos tengan la oportunidad de leerlo.
Otro buen modismo con referencias atómicas es si estás configurando algo incondicionalmente pero necesitas realizar algún tipo de limpieza con el valor anterior. En ese caso puedes decir:
MouseBall oldBall = mouseBall.getAndSet(newMouseBall);
// Cleanup code using oldBall
AtomicIntegers tienen estos beneficios y más; el método getAndIncrement () es maravilloso para los contadores compartidos globalmente, ya que puede garantizar que cada llamada le devolverá un valor distinto, independientemente del entrelazado de subprocesos. Rosca de seguridad con un mínimo de alboroto.