Лучшая практика: многопоточный шаблон с ленивой инициализацией
Идея состоит в том, чтобы создать фабричный класс, который возвращает разные экземпляры-одиночки, основанные на "типе". Многотонный рисунок. Кроме того, экземпляры-одиночки должны создаваться лениво.
Является ли приведенный ниже код безопасным? Использование ConcurrentHashMap делает это проще, но я хочу попробовать с HashMap.
public class Multiton {
private HashMap<String, Interface> interfaceMap;
private static class Holder {
public static final Multiton INSTANCE = new Multiton();
}
public static Multiton getInstance() {
return Holder.INSTANCE;
}
private Multiton(){
interfaceMap = new HashMap<String, Interface>();
}
public Interface getInterface(String key){
Interface value = interfaceMap.get(key);
if(null == value){
synchronized(interfaceMap){
// double check locking
if(interfaceMap.get(key) == null){
switch(key){
case TypeA : // initialize Type A interface here and put it to map
value = new TypeA();
interfaceMap.put(key, value);
break;
case TypeB : // initialize Type B interface here and put it to map
value = new TypeB();
interfaceMap.put(key, value);
break;
}
}
}
}
return interfaceMap.get(key);
}
}
1 ответ
Двойная проверка блокировки (в общем) не является резьбовой безопасностью. В частности, недостаточно выполнять синхронизацию только при записи на карту, поскольку это не мешает потоку считывателя получить доступ к состоянию, которое поток записи в настоящее время изменяет.
Если вы не можете использовать ConcurrentHashMap (например, потому что вы должны убедиться, что один TypeA создан, а не только один TypeA используется всеми клиентами), следующее должно работать и быть достаточно дешевым. В частности, он свободен от блокировки после создания ленивого объекта.
abstract class Lazy<T> {
private volatile T instance;
abstract T create();
void get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = create();
}
}
}
return instance;
}
}
class Multiton {
private final Map<String, Lazy<?>> map = new HashMap<>();
Multiton() {
map.put("A", new Lazy<A> {
A create() {
return new A();
}
}
map.put("B", new Lazy<B> {
B create() {
return new B();
}
}
// and so on for the other types
}
}
Для обеспечения качества продукции вы можете рассмотреть возможность использования выражений lamdba для фабричных методов и ввести безопасные ключи для карты (например, объекты класса запрашиваемого интерфейса).