Мне нужен ваш совет, проверка кода или улучшение моей реализации многотонного шаблона. Я хочу поддержку нескольких подключений для сервера mongodb.

public class MongoDatabaseFactory {
    private static volatile Map<String, MongoDatabase> connections = new ConcurrentHashMap<String, MongoDatabase>();

    public static MongoDatabase getDatabase(Databases database) throws MongoException {
        if (null == database) throw new MongoException("Database not found");
        if (null == database.name() || database.name().isEmpty()) throw new MongoException("Database not found");

        if (!connections.containsKey(database.name()) || null == connections.get(database.name())) {
            synchronized (database) {
                if (!connections.containsKey(database.name()) || null == connections.get(database.name())) {
                    connectDB(database);
                }
            }
        }

        if (!connections.get(database.name()).isAuthenticated()) {
            synchronized (database) {
                if (!connections.get(database.name()).isAuthenticated()) {
                    connectDB(database);
                }
            }
        }

        return connections.get(database.name());
    }
}

Что лучше всего подходит для многотонной схемы?

0
Hasan Ozgan 13 Мар 2014 в 13:52
MongoClient уже является пулом соединений, зачем вам это нужно? добавить дополнительный слой?
 – 
assylias
13 Мар 2014 в 14:13

2 ответа

Лучший ответ

Как говорит Марко Топольник , ваше текущее решение не является потокобезопасным.

Я воспринял это как небольшое упражнение и написал следующий универсальный многопоточный шаблон Multition. Разработан ли он для хорошей работы со многими потоками и подходит в случаях, когда создание объекта-значения обходится дорого. Обратите внимание, что я не уверен, что в вашем конкретном случае нет более простого решения.

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


public class ThreadSafeMultition <K, V> {
  private final ConcurrentHashMap<K, FutureTask<V>> map = new ConcurrentHashMap<K, FutureTask<V>>();
  private ValueFactory<K, V> factory;

  public ThreadSafeMultition(ValueFactory<K, V> factory) {
    this.factory = factory;
  }

  public V get(K key) throws InterruptedException, ExecutionException {
    FutureTask<V> f = map.get(key);
    if (f == null) {
      f = new FutureTask<V>(new FactoryCall(key));
      FutureTask<V> existing = map.putIfAbsent(key, f);
      if (existing != null)
        f = existing;
      else // Item added successfully. Now that exclusiveness is guaranteed, start value creation.
        f.run();
    } 

    return f.get();
  }

  public static interface ValueFactory<K, V> {
    public V create(K key) throws Exception;
  }

  private class FactoryCall implements Callable<V> {
    private K key;

    public FactoryCall(K key) {
      this.key = key;
    }

    @Override
    public V call() throws Exception {
      return factory.create(key);
    }    
  }
}
1
Eyal Schneider 13 Мар 2014 в 15:23

Эта строка не является поточно-ориентированной:

if (!connections.containsKey(database.name()) || null == connections.get(database.name()))

Здесь у вас будет гонка данных на хеш-карте, потому что вы не защищаете доступ к карте блокировкой. Вероятно, лучшим решением было бы переместить это в блок synchronized. Здесь не стоит беспокоиться о производительности, по крайней мере, без веских доказательств.

0
Marko Topolnik 13 Мар 2014 в 14:18
Ах, да, это было одним из моих самых больших разочарований в отношении того, что я мог сделать с помощью однострочника Clojure, но мог добиться этого только на Java с массивным и неуклюжим блоком кода, копируемым для каждого использования. Естественно, они ухватились за возможность использовать здесь лямбду.
 – 
Marko Topolnik
13 Мар 2014 в 19:01