Su código potencialmente llamar veryCostlyOperation (nombre) varias veces. El problema es que hay un paso no sincronizado después de buscar el mapa:
public void request(String name) {
Resource resource = resources.get(name);
if (resource == null) {
synchronized(this) {
//...
}
}
//...
}
el get() del mapa está sincronizado por el mapa, pero comprobar el resultado de nulo no está protegido por nada.Si varios subprocesos entran en este solicitando el mismo "nombre", todos verán un resultado nulo de resources.get(), hasta que realmente termine costoso Operación y lo coloque en el mapa de recursos.
Un enfoque más simple y funcional, pero menos escalable, sería ir con un mapa normal y sincronizar todo el método de solicitud. A menos que realmente resulte un problema en la práctica, elegiría el enfoque simple.
Para una mayor escalabilidad puede corregir su código verificando el mapa nuevamente después de sincronizado (esto) para atrapar el caso descrito anteriormente. Todavía no daría la mejor escalabilidad, ya que el sincronizado (esto) solo permite que un subproceso ejecute costosOperación, mientras que en muchos casos prácticos, solo desea evitar múltiples ejecuciones para el mismo recurso mientras permite solicitudes concurrentes al diferente recursos. En ese caso, necesita alguna facilidad para sincronizarse en el recurso que se solicita. Un ejemplo muy básico:
private static class ResourceEntry {
public Resource resource;
}
private Map<String, ResourceEntry> resources = new HashMap<String, ResourceEntry>();
public Resource request(String name) {
ResourceEntry entry;
synchronized (resources) {
entry = resources.get(name);
if (entry == null) {
// if no entry exists, allocate one and add it to map
entry = new ResourceEntry();
resources.put(name, entry);
}
}
// at this point we have a ResourceEntry, but it *may* be no loaded yet
synchronized (entry) {
Resource resource = entry.resource;
if (resource == null) {
// must create the resource
resource = costlyOperation(name);
entry.resource = resource;
}
return resource;
}
}
Este es solo un boceto. Básicamente, realiza una búsqueda sincrónica de ResourceEntry y luego se sincroniza en ResourceEntry para garantizar que el recurso específico solo se genere una vez.
Gracias por usar FutureTask, he visto la clase antes en el interior, pero nunca supe que tiene estas características. – Boris
El problema con esto es que podría estar llamando 'veryCostlyOperation' en múltiples recursos simultáneamente. El OP mencionó que no quería llamarlo en el mismo recurso dos veces, pero podría haber sido un comentario ajustado. Si se solicitan doce recursos de diferencia simultáneamente en su código, doce llamadas 'veryCostlyOperation' se realizarán en paralelo. Si de hecho son muy intensivos en memoria, podría quedarse sin memoria. –