Потокобезопасные мультитоны в Java
Дано следующее многотонное:
public class Multiton
{
private static final Multiton[] instances = new Multiton[...];
private Multiton(...)
{
//...
}
public static Multiton getInstance(int which)
{
if(instances[which] == null)
{
instances[which] = new Multiton(...);
}
return instances[which];
}
}
Как мы можем сохранить его потокобезопасным и ленивым без дорогостоящей синхронизации метода getInstance() и споров о блокировке с двойной проверкой? Эффективный способ для синглетонов упоминается здесь, но это, кажется, не распространяется на многотоны.
5 ответов:
Это даст вам threadsafe механизм хранения для ваших Мультитонов. Единственным недостатком является то, что можно создать Мультитон, который не будет использоваться в putIfAbsent() звони. Такая возможность мала, но она существует. Конечно, при малейшем шансе, что это произойдет, это все равно не причинит никакого вреда.
С положительной стороны, нет необходимости в предварительном размещении или инициализации и нет предопределенных ограничений размера.
private static ConcurrentHashMap<Integer, Multiton> instances = new ConcurrentHashMap<Integer, Multiton>(); public static Multiton getInstance(int which) { Multiton result = instances.get(which); if (result == null) { Multiton m = new Multiton(...); result = instances.putIfAbsent(which, m); if (result == null) result = m; } return result; }
Обновление: с Java 8 это может быть еще проще:
public class Multiton { private static final ConcurrentMap<String, Multiton> multitons = new ConcurrentHashMap<>(); private final String key; private Multiton(String key) { this.key = key; } public static Multiton getInstance(final String key) { return multitons.computeIfAbsent(key, Multiton::new); } }МММ, это хорошо!
ОРИГИНАЛЬНЫЙ ОТВЕТ
Это решение, которое строится на шаблоне Memoizer, как описано в JCiP. Он использует ConcurrentHashMap, как и один из других ответов, но вместо хранения непосредственно многотонных экземпляров, что может привести к созданию неиспользуемых экземпляров, он хранит вычисления, которые приводят к созданию многотонных экземпляров. Этот дополнительный слой решает проблему неиспользуемые экземпляры.
public class Multiton { private static final ConcurrentMap<Integer, Future<Multiton>> multitons = new ConcurrentHashMap<>(); private static final Callable<Multiton> creator = new Callable<Multiton>() { public Multiton call() { return new Multiton(); } }; private Multiton(Strnig key) {} public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { Future<Multiton> f = multitons.get(key); if (f == null) { FutureTask<Multiton> ft = new FutureTask<>(creator); f = multitons.putIfAbsent(key, ft); if (f == null) { f = ft; ft.run(); } } return f.get(); } }
Вы можете использовать массив блокировок, чтобы, по крайней мере, иметь возможность получать разные экземпляры одновременно:
private static final Multiton[] instances = new Multiton[...]; private static final Object[] locks = new Object[instances.length]; static { for (int i = 0; i < locks.length; i++) { locks[i] = new Object(); } } private Multiton(...) { //... } public static Multiton getInstance(int which) { synchronized(locks[which]) { if(instances[which] == null) { instances[which] = new Multiton(...); } return instances[which]; } }
С появлением Java 8 и некоторыми улучшениями в
ConcurrentMapи lambdas теперь можно реализоватьMultiton(и, вероятно, дажеSingleton) гораздо более аккуратным способом:public class Multiton { // Map from the index to the item. private static final ConcurrentMap<Integer, Multiton> multitons = new ConcurrentHashMap<>(); private Multiton() { // Possibly heavy construction. } // Get the instance associated with the specified key. public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { // Already made? Multiton m = multitons.get(key); if (m == null) { // Put it in - only create if still necessary. m = multitons.computeIfAbsent(key, k -> new Multiton()); } return m; } }Я подозреваю-хотя это заставило бы меня чувствовать себя некомфортно - что
getInstanceможно было бы далее свести к минимуму:// Get the instance associated with the specified key. public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { // Put it in - only create if still necessary. return multitons.computeIfAbsent(key, k -> new Multiton()); }
Вы ищете AtomicReferenceArray.
public class Multiton { private static final AtomicReferenceArray<Multiton> instances = new AtomicReferenceArray<Multiton>(1000); private Multiton() { } public static Multiton getInstance(int which) { // One there already? Multiton it = instances.get(which); if (it == null) { // Lazy make. Multiton newIt = new Multiton(); // Successful put? if ( instances.compareAndSet(which, null, newIt) ) { // Yes! it = newIt; } else { // One appeared as if by magic (another thread got there first). it = instances.get(which); } } return it; } }
Comments