2010-02-17 33 views
13

En nuestro sistema, tenemos un método que va a hacer un trabajo cuando se llama con un cierto ID:Java: ¿Sincronización en primitivas?

public void doWork(long id) { /* ... */ } 

Ahora, este trabajo se puede hacer al mismo tiempo para diferentes identificadores, pero si el método se llama con el mismo ID por 2 hilos, un hilo debe bloquearse hasta que se termine.

La solución más simple sería tener un mapa que se correlacione desde la ID larga con algún objeto arbitrario que podamos bloquear. Un problema que preveo con esto es que podemos tener toneladas de identificaciones en el sistema y este mapa seguirá creciendo todos los días.

Idealmente, creo que necesitamos un sistema en el que cada hilo busque un objeto de bloqueo, bloquee cuando sea posible, haga el trabajo y luego indique que hemos terminado con el bloqueo. Si está claro que nadie más está utilizando este bloqueo en particular, retírelo con seguridad del mapa de bloqueo para evitar la pérdida de memoria.

Imagino que este debe ser un escenario bastante común, así que espero que exista una solución existente. Alguien sabe de alguno?

+0

¿Cómo se producen estos números de identificación? Si hay algún corretaje de estos ID, simplemente distribuya objetos en lugar de primitivos y puede usarlos para bloquear/sincronizar. –

+0

Ver también http://stackoverflow.com/q/6616141/32453 – rogerdpack

Respuesta

15

Inventé una cosa así para mí hace algún tiempo. Lo llamo bloqueo de clase de equivalencia, es decir, se bloquea en todas las cosas que son iguales a lo dado. Puede obtenerlo from my github, y usarlo sujeto a la licencia de Apache 2, si lo desea, ¡o simplemente leerlo y olvidarlo!

+0

+1 para proporcionar una implementación gratuita. –

+2

Y vale cada centavo. ;) –

0

¿No sería suficiente utilizar un SynchronizedHashMap o Collections.synchronizedMap (Map m) del paquete java.util.concurrent en lugar de un HashMap simple donde las llamadas para recuperar e insertar no están sincronizadas?

algo como:

Map<Long,Object> myMap = new HashMap<Long,Object>(); 
Map<Long,Object> mySyncedMap=Collections.synchronizedMap(myMap); 
4

yo diría que ya se encuentra bastante lejos de tener una solución. Haga un LockManager que de forma perezosa y de referencia-cuenta-administra esos bloqueos para usted. Luego usarlo en doWork:

public void doWork(long id) { 
    LockObject lock = lockManager.GetMonitor(id); 
    try { 
     synchronized(lock) { 
      // ... 
     } 
    } finally { 
     lock.Release(); 
    } 
} 
+0

Empecé a arreglar el código, pero entonces - no es demasiado Java para solucionarlo ... (El objeto no tiene 'release') – Bozho

+0

Sí, esto es bastante similar a lo que tengo ahora, pero no confío en mí mismo lo suficiente para hacer esto bien. Las 2 piezas faltantes aquí son las implementaciones de las clases Lock y LockManager. –

+0

Para mí, parece una solución viable. Reemplace el 'Objeto' con alguna otra clase personalizada. –

0

optimización prematura es la raíz del mal

Prueba con un mapa (sincronizado).

Tal vez si crece demasiado grande, puede borrar su contenido a intervalos regulares.

+2

No lo llamaría prematuro. Sabemos de hecho que miles y miles de identificaciones únicas se utilizan todos los días. No quiero vaciar el mapa porque es bastante probable que se use al menos 1 bloqueo. Estoy pensando en utilizar un mapa donde se elimine la entrada más antigua cuando el mapa está lleno, pero incluso así no hay garantía de que no se use el bloqueo. –

-2

Sugiero que utilice las utilidades de java.util.concurrent, especialmente la clase AtomicLong. Ver related javadoc

+0

No creo que haya una clase en particular allí que me ayude. Estoy buscando una estrategia sobre cómo usar esas piezas juntas. –

+0

Por favor, lea la página: le ayudará a entender cómo usarlo –

+1

Philippe, él no necesita un largo atómico. Él necesita una multitud de cerraduras diferentes. –

4

Para empezar:

  1. No puede bloquear en una primitiva y
  2. no se bloquean en un largo a menos que esté cuidado de cómo se les construye. Se garantiza que los valores largos creados por autoboxing o Long.valueOf() en un cierto rango sean los mismos en toda la JVM, lo que significa que otros hilos podrían estar bloqueados en el mismo objeto Largo exacto y dándole una diafonía. Esto puede ser un error de concurrencia sutil (similar al bloqueo en cadenas internados).

Estamos hablando de una configuración de bloqueo de trama. Un extremo del continuo es un único bloqueo gigante para todos los identificadores, que será fácil y seguro pero no simultáneo. El otro extremo es un candado por identificación que es fácil (hasta cierto punto) y seguro y muy concurrente, pero puede requerir una gran cantidad de "objetos bloqueables" en la memoria (si aún no los tiene). En algún lugar en el medio está la idea de crear un bloqueo para una serie de identificadores: esto le permite ajustar la concurrencia en función de su entorno y tomar decisiones acerca de las compensaciones entre la memoria y la concurrencia.

ConcurrentHashMap se puede utilizar para lograr esto, ya que CHM se compone internamente de segmentos (submapas) y hay un bloqueo por segmento. Esto le da concurrencia igual al número de segmentos (que por defecto es 16 pero es configurable).

Hay un montón de otras soluciones posibles para particionar su espacio de ID y crear conjuntos de bloqueos, pero tiene razón al ser sensible a los problemas de limpieza y pérdida de memoria; cuidar de eso mientras se mantiene la concurrencia es complicado. Tendrá que utilizar algún tipo de referencia contando cada candado y gestionar el desalojo de los candados viejos con cuidado para evitar el desalojo de un candado que está en proceso de ser bloqueado. Si utiliza esta ruta, use ReentrantLock o ReentrantReadWriteLock (y no sincronizado en objetos) ya que le permite administrar explícitamente el bloqueo como un objeto y utilizar los métodos adicionales disponibles en él.

También hay algunas cosas sobre esto y un ejemplo de StripedMap en Java Concurrency in Practice sección 11.4.3.

+0

Heh, esperaba que aparecieras. ¿No hay mejor manera que mapear mi rango de ID a un número fijo de bloqueos? Idealmente, cada identificación tendría su propio bloqueo, pero estos bloqueos se eliminan cuando estoy seguro de que ya no son necesarios. De esta forma puedo obtener la máxima concurrencia mientras me aseguro de que el uso de la memoria no crezca indefinidamente. –

+0

Esto es exactamente lo que Ehcache con Terracota hace internamente (con bloqueos agrupados). Puedo decirte que equilibrar las restricciones de la corrección de la concurrencia, el rendimiento y el uso de la memoria es un problema difícil. Me gustaría tener una solución perfecta en el espacio de Ehcache para señalar aquí, pero aún no veo uno, sobre todo porque los bloqueos internos no están expuestos en la API de Ehcache -> eso puede suceder en el futuro. –

+0

'ConcurrentHashMap' puede usar menos bloqueos, pero no es necesario que mantenga uno durante toda la duración de la llamada' doWork (id) '. – Matthew

6

Puede intentar lo siguiente poco 'truco'

String str = UNIQUE_METHOD_PREFIX + Long.toString(id); 
synchornized(str.intern()) { .. } 

que es 100% garantizado para devolver la misma instancia.

El UNIQUE_METHOD_PREFIX, puede ser una constante codificada, o pueden obtenerse usando:

StackTraceElement ste = Thread.currentThread().getStackTrace()[0]; 
String uniquePrefix = ste.getDeclaringClass() + ":" +ste.getMethodName(); 

que garantizará que el bloqueo ocurre sólo en este método precisa. Eso es para evitar interbloqueos.

+0

Buena idea. Buen hack. –

+0

Estaba pensando en esto, pero ¿esto no solo empuja la fuga de memoria hacia el caché interno de Long o de String? –

+1

Bad hack, Long.valueOf puede devolver un objeto diferente cada vez, y el uso de string.intern() desperdicia el permspace, y cualquier otro código que use cadenas como lock puede causar deadlocks. – josefx

0

Se puede crear una lista o un conjunto de identificadores de activos y el uso de esperar y notificar:

List<Long> working; 
public void doWork(long id) { 
synchronized(working) 
{ 
    while(working.contains(id)) 
    { 
     working.wait(); 
    } 
    working.add(id)//lock 
} 
//do something 
synchronized(working) 
{ 
    working.remove(id);//unlock 
    working.notifyAll(); 
} 
} 

Problemas resueltos:

  • Solamente los hilos con la misma ID de esperar, todos los demás son concurrentes
  • Sin memoria Se filtrarán los "bloqueos" (Largo) al desbloquear
  • Funciona con autoboxing

problemas allí:

  • mientras/notifyAll puede causar pérdida de rendimiento con alto número de hilos
  • No réentrant
0

Aquí es donde me gustaría utilizar un mapa canonicalizing, que tiene su long ingrese y devuelve un objeto canónico Long que luego puede usar para sincronizar. He escrito sobre mapas de canonización here; simplemente reemplace String por Long (y para hacer su vida más fácil, deje que tome un long como parámetro).

Una vez que tenga el mapa canonicalizing, que iba a escribir el código de bloqueo vigilado así:

Long lockObject = canonMap.get(id); 
synchronized (lockObject) 
{ 
    // stuff 
} 

El mapa canonicalizing se aseguraría de que el mismo lockObject es devuelto por el mismo ID. Cuando no hay referencias activas al lockObject, serán elegibles para la recolección de basura, por lo que no se llenará la memoria de objetos innecesarios.

8

Puede intentar algo con un ReentrantLock, de modo que tenga un Map<Long,Lock>. Ahora después de lock.release() Puede probar lock.hasQueuedThreads(). Si eso devuelve falso, puedes eliminarlo del mapa.

Cuestiones relacionadas