WeakMultiton: обеспечение наличия только одного объекта для конкретной строки базы данных - PullRequest
1 голос
/ 19 ноября 2011

В моем приложении мне нужно убедиться, что для сущности, представляющей строку данных в базе данных, у меня есть не более одного Java-объекта, представляющего ее.

Недостаточно убедиться, что они равны (), поскольку яможет быть пойман проблемы когерентности.Так что в основном мне нужен мультитон;более того, мне не нужно хранить этот объект в памяти, когда он не нужен, поэтому я буду использовать слабые ссылки.

Я разработал это решение:

package com.example;

public class DbEntity {
    // a DbEntity holds a strong reference to its key, so as long as someone holds a
    // reference to it the key won't be evicted from the WeakHashMap
    private String key;

    public void setKey(String key) {
        this.key = key;
    }

    public String getKey() {
        return key;
    }

    //other stuff that makes this object actually useful.

}

package com.example;

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;

public class WeakMultiton {

    private ReentrantLock mapLock = new ReentrantLock();
    private WeakHashMap<String, WeakReference<DbEntity>> entityMap = new WeakHashMap<String, WeakReference<DbEntity>>();

    private void fill(String key, DbEntity object) throws Exception {
        // do slow stuff, typically fetch data from DB and fill the object.
    }

    public DbEntity get(String key) throws Exception {
        DbEntity result = null;
        WeakReference<DbEntity> resultRef = entityMap.get(key);
        if (resultRef != null){
            result = resultRef.get();
        }
        if (result == null){
            mapLock.lock();
            try {
                resultRef = entityMap.get(key);
                if (resultRef != null){
                    result = resultRef.get();
                }
                if (result == null){
                    result = new DbEntity();                
                    synchronized (result) {
                        // A DbEntity holds a strong reference to its key, so the key won't be evicted from the map
                        // as long as result is reachable.
                        entityMap.put(key, new WeakReference<DbEntity>(result));

                        // I unlock the map, but result is still locked.
                        // Keeping the map locked while querying the DB would serialize database calls!
                        // If someone tries to get the same DbEntity the method will wait to return until I get out of this synchronized block.
                        mapLock.unlock();

                        fill(key, result);

                        // I need the key to be exactly this String, not just an equal one!!
                        result.setKey(key);
                    }
                }
                } finally {
                // I have to check since I could have already released the lock.
                if (mapLock.isHeldByCurrentThread()){
                    mapLock.unlock();
                }
            }
        }
        // I synchronize on result since some other thread could have instantiated it but still being busy initializing it.
        // A performance penality, but still better than synchronizing on the whole map.
        synchronized (result) {
            return result;
        }
    }
}

Будет создан экземпляр WeakMultitonтолько в обертке базы данных (единственная точка доступа к базе данных) и ее get (ключ String), конечно, будет единственным способом получения DbEntity.Теперь, насколько мне известно, это должно сработать, но, поскольку этот материал для меня довольно новый, я боюсь, что мог бы что-то наблюдать за синхронизацией или слабыми ссылками!Можете ли вы заметить какой-либо недостаток или предложить улучшения?

1 Ответ

1 голос
/ 20 ноября 2011

Я узнал о MapMaker в Guava и написал этот обобщенный AbstractWeakMultiton:

package com.example;

import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import com.google.common.collect.MapMaker;

public abstract class AbstractWeakMultiton<K,V, E extends Exception> {

    private ReentrantLock mapLock = new ReentrantLock();
    private Map<K, V> entityMap = new MapMaker().concurrencyLevel(1).weakValues().<K,V>makeMap();

    protected abstract void fill(K key, V value) throws E;
    protected abstract V instantiate(K key);
    protected abstract boolean isNullObject(V value);


    public V get(K key) throws E {
        V result = null;
        result = entityMap.get(key);
        if (result == null){
            mapLock.lock();
            try {
                result = entityMap.get(key);
                if (result == null){
                    result = this.instantiate(key);             
                    synchronized (result) {
                        entityMap.put(key, result);
                        // I unlock the map, but result is still locked.
                        // Keeping the map locked while querying the DB would serialize database calls!
                        // If someone tries to get the same object the method will wait to return until I get out of this synchronized block.
                        mapLock.unlock();

                        fill(key, result);

                    }
                }
            } finally {
                // I have to check since the exception could have been thrown after I had already released the lock.
                if (mapLock.isHeldByCurrentThread()){
                    mapLock.unlock();
                }
            }
        }
        // I synchronize on result since some other thread could have instantiated it but still being busy initializing it.
        // A performance penalty, but still better than synchronizing on the whole map.
        synchronized (result) {
            // I couldn't have a null result because I needed to synchronize on it,
            // so now I check whether it's a mock object and return null in case.
            return isNullObject(result)?null:result;
        }
    }
}

Он имеет следующие преимущества по сравнению с моей предыдущей попыткой:

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

С другой стороны, это зависит от довольно громоздкой библиотеки Guava, в то время как первое решение использовало только классы из среды выполнения. Я могу жить с этим.

Я, очевидно, все еще ищу дальнейшие улучшения и обнаружение ошибок, и в основном все, что отвечает на самый важный вопрос: это будет работать?

...