2010-06-02 9 views
6

Supongamos que tenemos una clase llamada AccountService que administra el estado de las cuentas.¿Mejores formas de escribir un método que actualice dos objetos en un entorno Java multiproceso?

AccountService se define como

interface AccountService{ 
public void debit(account); 
public void credit(account); 
public void transfer(Account account, Account account1); 

} 

Dada esta definición, ¿cuál es la mejor manera de implementar la transferencia() de modo que usted puede garantizar que la transferencia es una operación atómica.

Estoy interesado en recibir respuestas que hacen referencia a código Java 1.4, así como las respuestas que podrían utilizar los recursos de java.util.concurrent en Java 5

Respuesta

11

Sincronizar en ambos Account objetos y hacer la transferencia. Asegúrese de sincronizar siempre en el mismo orden. Para hacerlo, haga que Account implemente Comparable, ordene las dos cuentas y sincronice en ese orden.

Si no pide las cuentas, se corre la posibilidad de estancamiento si uno transferencias de rosca de la A a B y otro transferencias de B a A.

Este ejemplo exacta se discute en la página 207 de Java Concurrency in Practice, un libro crítico para cualquiera que realice un desarrollo de Java multiproceso. El código de ejemplo está disponible en el publisher's website:

+0

+1 para evitar el interbloqueo – unbeli

+1

+1: Bloquear siempre los objetos en el mismo orden no puede sobreestresarse. – Powerlord

+0

si el rendimiento no es crítico, y hay un único Servicio de cuenta, también se podría simplemente 'sincronizar' el método de transferencia. – aioobe

2

Probablemente necesita tener un soporte completo de transacciones (si se trata de una aplicación real, por supuesto).

La dificultad de la solución apenas depende de su entorno. Describa su sistema en detalle e intentaremos ayudarlo (¿qué tipo de aplicación? ¿Utiliza servidor web? ¿Qué servidor web? ¿Qué se utiliza para almacenar datos? Y así sucesivamente)

+0

+1 para abordar los problemas de falta de sincronización con las transacciones atómicas. –

0

no podrías evitar tener que sincronizar mediante una AtomicReference<Double> para el saldo de la cuenta, junto con get() y set()?

+1

Aún tendría que sincronizar los accesos a esa 'Referencia Atómica ', ya que la operación 'bal.set (bal.get() + creditAmt)' no es atómica: algo podría cambiar la referencia mantenida por bal entre la llamada a 'get()' y 'set()'. Pero es más una cuestión de vincular las dos operaciones (débito y crédito) en una sola operación atómica, especialmente para que si una falla, la otra falle también (retroceso). –

+0

Asegurándome de que entiendo correctamente ... el problema potencial usando variables atómicas sin sincronización no es lo que hace mi esposa (cambiando el valor de bal entre mis llamadas a get() y set()), sino más bien a lo que mi ex esposa does (cambiando la referencia a bal entre mis llamadas a get() y set())? – Pete

+0

"valor" y "referencia" son difíciles de usar en este contexto ya que tiene una referencia ('AtomicReference - bal') a una referencia (' Double - bal.value') a un valor doble ('double - bal.value .value'). La referencia 'bal' sería final, por lo que no es un problema. El problema de la sincronización consiste en hacer las operaciones matemáticas, donde primero debe 1) 'obtener()' el Doble de AtomicReference, 2) hacer cálculos en él, y 3) 'establecer()' el valor de nuevo. Si no haces 1-2-3 atómico, podrías hacer que el Hilo A haga 1 y 2, luego el Hilo B haga 1, luego Hilo A haga 3, luego Hilo B haga 2 y 3 matando los cambios de A. –

1

Si puede garantizar que todos los accesos se realizan a través del método de transferencia, entonces probablemente el enfoque más fácil sea simplemente hacer que la transferencia sea un método sincronizado. Esto será seguro para subprocesos porque esto garantiza que solo un subproceso ejecutará el método de transferencia en cualquier momento.

Si otros métodos también pueden acceder al AccountService, entonces puede decidir que todos ellos utilicen un único bloqueo global. Una manera fácil de hacerlo es rodear todo el código que accede al AccountService en un bloque synchronized (X) {...} donde X es una instancia de objeto shared/singleton (que podría ser la instancia de AccountService en sí misma). Esto será seguro para subprocesos, ya que solo un subproceso accederá al Servicio de cuenta en cualquier momento, incluso si tienen diferentes métodos.

Si eso aún no es suficiente, entonces tendrá que utilizar enfoques de bloqueo más sofisticados. Un enfoque común sería bloquear las cuentas individualmente antes de modificarlas ... pero debe tener mucho cuidado de tomar las cerraduras en un orden coherente (por ejemplo, mediante la identificación de la cuenta), de lo contrario, se encontrará con puntos muertos.

Finalmente si AccountService es un servicio remoto, entonces usted está en el territorio de bloqueo distribuido ... a menos que tenga un doctorado en informática y años de presupuesto de investigación para quemarlo, probablemente debería evitar ir allí.

+0

P.S. Para simplificar y generalizar, apostaría por el bloqueo global hasta llegar a un problema de rendimiento/escalabilidad (¡que puede ser nunca!) Y solo entonces comenzar a pensar en pasar al bloqueo a nivel de cuenta. – mikera

+0

Aprecio la integridad de su respuesta. Lo que mencionaste en tu comentario es precisamente hacia dónde me dirigía. Una vez que tiene que entrar en el bloqueo del nivel de la cuenta, parece que hay una necesidad de un esquema más elaborado que el simple uso de la palabra clave 'sincronizada'. –

+0

@DanielHonig: Es cierto que es bueno evitar la optimización prematura, pero en muchos o la mayoría de los Sistemas de Procesamiento de Transacciones (donde el trabajo de la máquina es básicamente hacer esto todo el día en millones de registros) el diseñador nunca intentaría usar un mutex global A menudo se diseñan con muchos (¡muchos!) Núcleos y tratan de trabajar en paralelo tanto como sea posible. Por lo tanto, es bueno saber por qué el bloqueo basado en la cuenta es útil en este dominio. –

Cuestiones relacionadas