Потоковые безопасные мультитоны в 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() и споров о двойной проверке блокировки? Эффективный способ для синглонов упоминается здесь, но это, похоже, не распространяется на многотонные.
Ответы
Ответ 1
Это даст вам механизм хранения потокобезопасности для ваших Multitons. Единственным недостатком является то, что можно создать Multiton, который не будет использоваться в вызове 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;
}
Ответ 2
UPDATE: с 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 как один из других ответов, но вместо хранения экземпляров Multiton напрямую, что может привести к созданию неиспользуемых экземпляров, оно хранит вычисления, которые приводят к созданию Multiton. Этот дополнительный слой решает проблему неиспользуемых экземпляров.
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();
}
}
Ответ 3
Вы можете использовать массив блокировок, чтобы, по крайней мере, иметь возможность одновременно получать разные экземпляры:
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];
}
}
Ответ 4
С появлением Java 8 и некоторыми улучшениями в ConcurrentMap
и lambdas теперь можно реализовать Multiton
(и, возможно, даже a 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());
}
Ответ 5
Вы ищете 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;
}
}