在我的应用程序中,我需要确保对于代表数据库中数据行的实体我最多有一个 java 对象来代表它。
确保它们是 equals() 是不够的,因为我可能会遇到一致性问题。所以基本上我需要一个多吨;此外,当没有必要时,我不需要将此对象保留在内存中,因此我将使用弱引用。
我设计了这个解决方案:
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 key) 当然是检索 DbEntity 的唯一方法。现在,据我所知,这应该可行,但由于这些东西对我来说相当新,我担心我可能会监督有关同步或弱引用的事情!您能发现任何缺陷或提出改进建议吗?
我发现了 guava 的 MapMaker 并编写了这个通用的 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 库,而第一个解决方案仅使用运行时环境中的类。我可以忍受这一点。
我显然仍在寻找进一步的改进和错误发现,以及基本上回答最重要问题的所有内容:它会起作用吗?
我是一名优秀的程序员,十分优秀!