java - programacion - Sincronización en un valor entero
manual de programacion android pdf (9)
¿Qué tal un ConcurrentHashMap con los objetos enteros como claves?
Posible duplicado:
Cuál es la mejor manera de aumentar el número de bloqueos en java
Supongamos que quiero bloquear en función de un valor entero de identificación. En este caso, hay una función que extrae un valor de un caché y realiza una recuperación / almacenamiento bastante caro en el caché si el valor no está allí.
El código existente no está sincronizado y podría desencadenar varias operaciones de recuperación / almacenamiento:
//psuedocode
public Page getPage (Integer id){
Page p = cache.get(id);
if (p==null)
{
p=getFromDataBase(id);
cache.store(p);
}
}
Lo que me gustaría hacer es sincronizar la recuperación en el ID, por ejemplo
if (p==null)
{
synchronized (id)
{
..retrieve, store
}
}
Lamentablemente, esto no funcionará porque 2 llamadas separadas pueden tener el mismo valor de Id. Entero, pero un objeto Entero diferente, por lo que no compartirán el bloqueo, y no ocurrirá ninguna sincronización.
¿Hay una forma simple de asegurar que tienes la misma instancia de Integer? Por ejemplo, esto funcionará:
syncrhonized (Integer.valueOf(id.intValue())){
El javadoc para Integer.valueOf () parece implicar que es probable que obtenga la misma instancia, pero eso no parece una garantía:
Devuelve una instancia de enteros que representa el valor int especificado. Si no se requiere una nueva instancia de Entero, este método generalmente se debe usar con preferencia al Entero del constructor (int), ya que es probable que este método produzca un mejor rendimiento de espacio y tiempo almacenando en caché los valores frecuentemente solicitados.
Entonces, ¿hay alguna sugerencia sobre cómo obtener una instancia de Entero que garantice que es la misma, que no sean las soluciones más elaboradas, como mantener un WeakHashMap de objetos de bloqueo en clave de int? (No hay nada de malo en eso, parece que debe haber un obvio de una línea que me falta).
Como puede ver en la variedad de respuestas, hay varias formas de desollar a este gato:
- El enfoque de Goetz et al de mantener un caché de FutureTasks funciona bastante bien en situaciones como esta en las que "almacenas en caché algo de todos modos", así que no te importa construir un mapa de objetos FutureTask (y si te importa que el mapa crezca, al menos es fácil hacer una poda concurrente)
- Como respuesta general a "cómo bloquear la identificación", el enfoque descrito por Antonio tiene la ventaja de que es obvio cuando se agrega / elimina el mapa de bloqueos.
Es posible que deba vigilar un posible problema con la implementación de Antonio, es decir, que notifyAll () activará los hilos que esperan en todos los ID cuando uno de ellos esté disponible, lo que puede no escalarse muy bien en caso de alta contención. En principio, creo que puedes solucionarlo teniendo un objeto Condition para cada ID actualmente bloqueada, que es lo que estás esperando / señal. Por supuesto, si en la práctica rara vez se espera más de una identificación en un momento dado, entonces esto no es un problema.
Consulte la sección 5.6 en Simultánea de Java en la práctica : "Creación de una memoria caché de resultados escalable y eficiente". Se trata del problema exacto que intentas resolver. En particular, revisa el patrón de memoizer.
texto alternativo http://www.cs.umd.edu/class/fall2008/cmsc433/jcipMed.jpg
Puede echarle un vistazo a este código para crear un mutex a partir de una ID. El código se escribió para las ID de cadena, pero podría editarse fácilmente para objetos enteros.
Realmente no desea sincronizar en un Integer
, ya que no tiene control sobre qué instancias son iguales y qué instancias son diferentes. Java simplemente no proporciona esa facilidad (a menos que use enteros en un rango pequeño) que es confiable en diferentes JVM. Si realmente debe sincronizar en un Entero, entonces necesita mantener un Mapa o Conjunto de Entero para que pueda garantizar que está obteniendo la instancia exacta que desea.
Sería mejor crear un objeto nuevo, tal vez almacenado en un HashMap
que esté codificado por el Integer
, para sincronizarlo. Algo como esto:
public Page getPage(Integer id) {
Page p = cache.get(id);
if (p == null) {
synchronized (getCacheSyncObject(id)) {
p = getFromDataBase(id);
cache.store(p);
}
}
}
private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>();
private Object getCacheSyncObject(final Integer id) {
locks.putIfAbsent(id, id);
return locks.get(id);
}
Para explicar este código, usa ConcurrentMap
, que permite el uso de putIfAbsent
. Podrías hacer esto:
locks.putIfAbsent(id, new Object());
pero luego incurre en el (pequeño) costo de crear un Objeto para cada acceso. Para evitar eso, simplemente guardo el entero en el Map
. ¿Qué logra esto? ¿Por qué es esto diferente de solo usar el Integer en sí?
Cuando haces un get()
desde un Map
, las claves se comparan con equals()
(o al menos el método utilizado es el equivalente a usar equals()
). Dos instancias de enteros diferentes del mismo valor serán iguales entre sí. Por lo tanto, puede pasar cualquier cantidad de instancias de Entero diferentes de " new Integer(5)
" como el parámetro para getCacheSyncObject
y siempre obtendrá solo la primera instancia que se pasó en ese valor.
Hay razones por las que es posible que no desee sincronizar en Integer
... puede entrar en interbloqueos si varios subprocesos se sincronizan en objetos Integer
y, por lo tanto, involuntariamente usan los mismos bloqueos cuando quieren usar bloqueos diferentes. Puede solucionar este riesgo utilizando el
locks.putIfAbsent(id, new Object());
versión y por lo tanto incurrir en un (muy) pequeño costo para cada acceso a la memoria caché. Al hacer esto, usted garantiza que esta clase hará su sincronización en un objeto que no se sincronizará en ninguna otra clase. Siempre es una buena cosa.
Steve,
su código propuesto tiene un montón de problemas con la sincronización. (Antonio también lo hace).
Para resumir:
- Necesita almacenar en caché un objeto costoso.
- Debe asegurarse de que mientras un hilo está recuperando, otro hilo tampoco intenta recuperar el mismo objeto.
- Para n-hilos, todos los intentos de obtener el objeto solo 1 objeto se recuperan y devuelven.
- Eso para los hilos que solicitan diferentes objetos que no compiten entre sí.
pseudocódigo para que esto suceda (utilizando un ConcurrentHashMap como caché):
ConcurrentMap<Integer, java.util.concurrent.Future<Page>> cache = new ConcurrentHashMap<Integer, java.util.concurrent.Future<Page>>;
public Page getPage(Integer id) {
Future<Page> myFuture = new Future<Page>();
cache.putIfAbsent(id, myFuture);
Future<Page> actualFuture = cache.get(id);
if ( actualFuture == myFuture ) {
// I am the first w00t!
Page page = getFromDataBase(id);
myFuture.set(page);
}
return actualFuture.get();
}
Nota:
- java.util.concurrent.Future es una interfaz
- java.util.concurrent.Future en realidad no tiene un conjunto () pero mira las clases existentes que implementan Future para comprender cómo implementar tu propio futuro (o usa FutureTask)
- Posiblemente sea una buena idea empujar la recuperación real a un hilo de trabajo.
Usar sincronización en un Entero suena realmente mal por diseño.
Si necesita sincronizar cada elemento individualmente solo durante la recuperación / almacenamiento, puede crear un conjunto y almacenar allí los elementos actualmente bloqueados. En otras palabras,
// this contains only those IDs that are currently locked, that is, this
// will contain only very few IDs most of the time
Set<Integer> activeIds = ...
Object retrieve(Integer id) {
// acquire "lock" on item #id
synchronized(activeIds) {
while(activeIds.contains(id)) {
try {
activeIds.wait();
} catch(InterruptedExcption e){...}
}
activeIds.add(id);
}
try {
// do the retrieve here...
return value;
} finally {
// release lock on item #id
synchronized(activeIds) {
activeIds.remove(id);
activeIds.notifyAll();
}
}
}
Lo mismo va a la tienda.
La conclusión es: no hay una sola línea de código que resuelva este problema exactamente de la manera que usted necesita.
Use un mapa seguro para subprocesos, como ConcurrentHashMap
. Esto le permitirá manipular un mapa de forma segura, pero use un bloqueo diferente para realizar el cálculo real. De esta forma, puede tener múltiples cálculos funcionando simultáneamente con un solo mapa.
Utilice ConcurrentMap.putIfAbsent
, pero en lugar de colocar el valor real, utilice un Future
con una construcción computacionalmente liviana. Posiblemente la implementación FutureTask
. Ejecute el cálculo y luego get
el resultado, que se bloqueará de manera segura hasta que esté listo.
Integer.valueOf()
solo devuelve instancias en caché para un rango limitado. No ha especificado su rango, pero en general, esto no funcionará.
Sin embargo, le recomiendo que no tome este enfoque, incluso si sus valores están en el rango correcto. Como estas instancias de Integer
caché están disponibles para cualquier código, no puede controlar completamente la sincronización, lo que podría llevar a un punto muerto. Este es el mismo problema que las personas intentan bloquear con el resultado de String.intern()
.
El mejor bloqueo es una variable privada. Como solo su código puede hacer referencia a él, puede garantizar que no se produzcan bloqueos.
Por cierto, usar un WeakHashMap
tampoco funcionará. Si la instancia que sirve como clave no tiene referencia, será basura recolectada. Y si está fuertemente referenciado, puede usarlo directamente.