WeakMultiton: ensuring there's only one object for a specific database row

大憨熊 提交于 2019-12-11 10:36:37

问题


In my application I need to ensure that for an entity representing a data row in a database I have at most one java object representing it.

Ensuring that they are equals() is not enough, since I could get caught by coherency problems. So basically I need a multiton; moreover, I need not to keep this object in memory when it is not necessary, so I will be using weak references.

I have devised this solution:

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 will be instantiated only in the database wrapper (single point of access to the database) and its get(String key) will of course be the only way to retrieve a DbEntity. Now, to the best of my knowledge this should work, but since this stuff is pretty new to me, I fear I could be overseeing something about the synchronization or the weak references! Can you spot any flaw or suggest improvements?


回答1:


I found out about guava's MapMaker and wrote this generic 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;
        }
    }
}

It has the following advantages to my earlier try:

It does not depend on the fact that values hold a strong reference to the key It does not need to do the awkward double checking for expired weak references It is reusable

On the other hand, it depends on the rather beefy Guava library, while the first solution used just classes from the runtime environment. I can live with that.

I'm obviously still looking for further improvements and error spotting, and basically everything that answers the most important question: will it work?



来源:https://stackoverflow.com/questions/8189148/weakmultiton-ensuring-theres-only-one-object-for-a-specific-database-row

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!