带有弱引用值的HashMap

28

我正在为持久存储的对象实现一个缓存。思路如下:

  • getObjectFromPersistence(long id); ///需要约3秒
  • getObjectFromCache(long id) //立即返回

并且有一个方法:getObject(long id),其伪代码如下:

synchronized(this){
    CustomObject result= getObjectFromCache(id)
    if (result==null){
       result=getObjectFromPersistence(id);
       addToCache(result);
    }
    return result;
}

但是我需要让Garbage Collector回收CustomObject。到目前为止,我使用了一个HashMap<Long,WeakReference<CustomObject>进行实现。问题是随着时间的推移,HashMap变得充满了空的WeakReferences

我已经检查过WeakHashMap,但那里的键是弱引用(而值仍然是强引用),所以用WeakReferences作为长整型没有意义。

有什么最好的解决方案来解决这个问题吗?是否有某种“反向WeakHashMap”或类似的东西?

谢谢


2
你有没有看过Google Guava库中的缓存功能?我认为它可能包含你正在寻找的一些功能。http://code.google.com/p/guava-libraries/wiki/CachesExplained#Reference-based_Eviction - Henrik Aasted Sørensen
8个回答

31

你可以使用GuavaMapMaker来实现:

ConcurrentMap<Long, CustomObject> graphs = new MapMaker()
   .weakValues()
   .makeMap();
你甚至可以用以下代码替换makeMap()来包含计算部分:
   .makeComputingMap(
       new Function<Long, CustomObject>() {
         public CustomObject apply(Long id) {
           return getObjectFromPersistence(id);
         }
       });

鉴于您写的内容看起来很像缓存,更专业、更特定的Cache(通过CacheBuilder构建)可能更加适合您。它并没有直接实现Map接口,但提供了更多可能对缓存有用的控制选项。

您可以参考这个链接了解如何使用CacheBuilder,以下是一份快速访问的示例:

LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
   .maximumSize(100)
   .expireAfterWrite(10, TimeUnit.MINUTES)
   .build(
       new CacheLoader<Integer, String>() {
           @Override
           public String load(Integer id) throws Exception {
               return "value";
           }
       }
   ); 

6
在构建WeakReference时,会将其添加到在构建时提供的ReferenceQueue中,当收集引用时。
每次访问缓存时,您可以pollReferenceQueue,并持有一个HashMap<WeakReference<CustomObject>,Long>,以便知道如果在队列中找到引用,则应该删除哪个条目。
或者,如果不经常使用缓存,则可以在单独的线程中监视队列

4
你尝试过使用android.util.LruCache吗?它是一个SDK11类,但也在兼容包中作为android.support.v4.util.LruCache存在。它没有实现java.util.Map,但工作方式类似于Map,你可以定义它将占用多少内存,并且它会自动清除旧的(未使用的)缓存对象。

2
我认为最好的选择(如果不想依赖于Guava)是使用一个自定义的WeakReference子类来记住其ID,这样您的清理线程可以在清理WeakReferences时删除弱引用值。
弱引用的实现,需要有必要的ReferenceQueue和清理线程,可能会像以下代码一样:
class CustomObjectAccess {

    private static final ReferenceQueue<CustomObject> releasedCustomObjects = 
                                                                  new ReferenceQueue<>();

    static {
        Thread cleanupThread = new Thread("CustomObject cleanup thread")                  
            while (true) {
                CustomObjectWeakReference freed = (CustomObjectWeakReference) 
                                CustomObjectWeakReference.releasedCustomObjects.remove();
                cache.remove(freed.id);
            }
        };
        cleanupThread.start();
    }

    private Map<CustomObjectID, CustomObjectWeakReference> cache;

    public CustomObject get(CustomObjectID id) {
        synchronized(this){
            CustomObject result= getFromCache(id);
            if (result==null) {
                result=getObjectFromPersistence(id);
                addToCache(result);
            }
        }
        return result;
    }

    private addToCache(CustomObject co) {
        cache.put(CustomObject.getID(), new CustomObjectWeakReference(co));
    }

    private getFromCache(CustomObjectID id) {
        WeakReference<CustomObject> weak = cache.get(id);
        if (weak != null) {
            return weak.get();
        }
        return null;
    }

    class CustomObjectWeakReference extends WeakReference<CustomObject> {

        private final CustomObjectID id;

        CustomObjectWeakReference(CustomObject co) {
            super(co, releasedCustomObjects);
            this.id = co.getID();
        }
    }
}

2

2

您可以定期启动一次“清理”线程。也许当您的地图大小超过一个阈值时,但最多每5分钟进行一次......之类的操作。

保持清理周期短,以免阻塞主要功能。


1
或者您可以在每个请求中清理x个条目。这样更容易实现,而且不会停止整个系统。 - John Dvorak
太好了,Jan。这应该可以用低复杂度完成工作。 - Fildor
好主意,@JanDvorak。有一个缺点:不经常访问的缓存可能会比预期的时间更长地保留未使用的数据。只要缓存经常使用,那就是一个完全可以接受的策略。 - Joachim Sauer

0

0

我需要存储标记的弱对象,想到可以使用WeakHashMap<T, String>代替WeakHashMap<String, T>

这是Kotlin代码,但同样适用于Java:

abstract class InstanceFactory<T> {
    @Volatile
    private var instances: MutableMap<T, String> = WeakHashMap<T, String>()

    protected fun getOrCreate(tag: String = SINGLETON, creator: () -> T): T =
        findByTag(tag)?.let {
            it
        } ?: synchronized(this) {
            findByTag(tag)?.let {
                it
            } ?: run {
                creator().also {
                    instances[it] = tag
                }
            }
        }

    private fun findByTag(tag: String): T? = instances.entries.find { it.value == tag }?.key

    companion object {
        const val SINGLETON = "singleton"
    }
}

这可以按如下方式使用:

class Thing(private val dependency: Dep) { ... }

class ThingFactory(private val dependency: Dep) : InstanceFactory<Thing>() {

    createInstance(tag: String): Thing = getOrCreate(tag) { Thing(dependency) }

}

简单的单例可以像这样实现:

object ThingFactory {
    getInstance(dependency: Dependency): Thing = getOrCreate { Thing(dependency) }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接