Ваш код потенциально может вызывать veryCostlyOperation (name) несколько раз.Проблема в том, что после просмотра карты возникает несинхронизированный шаг:
public void request(String name) {
Resource resource = resources.get(name);
if (resource == null) {
synchronized(this) {
//...
}
}
//...
}
Функция get () из карты синхронизируется картой, но проверка результата на нулевое значение ничем не защищена.Если несколько потоков вводят это, запрашивая одно и то же «имя», все они будут видеть нулевой результат от resources.get (), пока один из них фактически не завершит costlyOperation и не поместит ресурс в карту ресурсов.
Более простой ирабочий, но менее масштабируемый подход - использовать карту нормалей и синхронизировать весь метод запроса.Если это на самом деле не является проблемой на практике, я бы выбрал простой подход.
Для более высокой масштабируемости вы можете исправить свой код, проверив карту снова после синхронизации (это), чтобы поймать случайобрисовано в общих чертах выше.Это все равно не обеспечит наилучшую масштабируемость, поскольку синхронизированный (это) позволяет только одному потоку выполнять costlyOperation, тогда как во многих практических случаях вы хотите только предотвратить несколько выполнений для одного и того же ресурса , допуская одновременныйзапросы к различным ресурсам.В этом случае вам нужно какое-то средство для синхронизации на запрашиваемом ресурсе.Очень простой пример:
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;
}
}
Это всего лишь грубый набросок.По сути, он выполняет синхронизированный поиск для ResourceEntry, и , а затем синхронизируется с ResourceEntry, чтобы гарантировать, что определенный ресурс создается только один раз .