我需要一个并发的对象缓存,其中每个实例都包含一个唯一的id
(可能还有一些额外的信息,为了简单起见,在下面的代码片段中省略了),并且不能创建比相应的ID,
和
一旦没有其他对象引用它们,我也需要GC对象(即保持内存foorprint尽可能低),所以我想使用WeakReference
,而不是{{1 }}的
在下面的工厂方法示例中,SoftReference
不是通用类型 - 相反,它可以被视为具有T
类型id
字段的任意类,所有ID都是独一无二的。每个值(类型String
)都映射到相应的Reference<T>
:
id
static final ConcurrentMap<String, WeakReference<T>> INSTANCES = new ConcurrentHashMap<>();
@NotNull
public static T from(@NotNull final String id) {
final AtomicReference<T> instanceRef = new AtomicReference<>();
final T newInstance = new T(id);
INSTANCES.putIfAbsent(id, new WeakReference<>(newInstance));
/*
* At this point, the mapping is guaranteed to exist.
*/
INSTANCES.computeIfPresent(id, (k, ref) -> {
final T oldInstance = ref.get();
if (oldInstance == null) {
/*
* The object referenced by ref has been GC'ed.
*/
instanceRef.set(newInstance);
return new WeakReference<>(newInstance);
}
instanceRef.set(oldInstance);
return ref;
});
return instanceRef.get();
}
的主题一旦被清除就需要GC(即引用对象GC'ed)超出了这个问题的范围 - 在生产代码中,这是实现的使用参考队列。
WeakReference
仅用于从lambda外部返回一个值(在与工厂方法本身相同的线程中执行)。
在代码运行成功几周后,我收到AtomicReference
来自额外的NPE
检查IntelliJ IDEA,这要归功于null
注释:
java.lang.IllegalStateException:@NotNull方法com / example / T.from必须不返回null
实际上,这意味着未在任何一个分支中设置@NotNull
值,或者未调用整个instanceRef
方法。
我看到的竞争条件的唯一可能性是在computeIfPresent(...)
和putIfAbsent(...)
调用之间的某个位置删除了映射条目(从单独的线程处理引用队列到GC的实例)。
我缺少一个比赛条件的额外空间吗?
答案 0 :(得分:1)
你必须记住,不仅可以发生其他线程,还可以发生GC。考虑一下这个片段:
instanceRef.set(oldInstance);
return ref;
});
// Here!!!!!
return instanceRef.get();
如果GC在Here
点开始,你认为会有什么影响?
我怀疑您的错误在@NotNull
,因为此方法可以返回null
。
如果最终instanceRef.get()
返回null
(如暗示的那样),则可以进行以下陈述。
密钥 存在且oldInstance
已成为GCd。记录肯定非空newInstance
。
// This line MUST be executed.
instanceRef.set(newInstance);
密钥 存在且oldInstance
不已成为GCd。记录肯定非空oldInstance
。
// This line MUST be executed.
instanceRef.set(oldInstance);
因此,当调用putIfAbsent
但在执行computeIfPresent
时消失实例时,可能会出现此问题。如果在putIfAbsent
和computeIfPresent
之间删除了某个项目,则可能会出现此情况。但是,在没有发生删除时找到返回null
的路线很困难。
您或许可以确保所引用的项目始终记录在参考中。
@NotNull
public static Thing fromMe(@NotNull final String id) {
// Keep track of the thing I've created (if any)
// Use AtomicReference as a mutable final.
// NB: Also delays GC as a hard reference is held.
final AtomicReference<Thing> thing = new AtomicReference<>();
// Make the map entry if not exists.
INSTANCES.computeIfAbsent(id,
// New one only made if not present.
r -> new WeakReference<>(newThing(thing, id)));
// Grab it - whatever it's contents.
// NB: Parallel deletions will cause a NPE here.
trackThing(thing, INSTANCES.get(id).get());
// Has it been GC'd
if (thing.get() == null) {
// Make it again!
INSTANCES.put(id, new WeakReference<>(newThing(thing, id)));
}
return thing.get();
}
// Makes a new Thing - keeping track of the new one in the reference.
static Thing newThing(AtomicReference<Thing> thing, String id) {
// Make the new Thing.
return trackThing(thing, new Thing(id));
}
// Tracks the Thing in the Atomic.
static Thing trackThing(AtomicReference<Thing> thing, Thing it) {
// Keep track of it.
thing.set(it);
return it;
}