使用 WeakReference 的并发缓存抛出 NPE
Concurrent cache using WeakReference's throws an NPE
我需要一个对象的并发缓存,其中每个实例都包装一个唯一的 id
(可能还有一些额外的信息,为简单起见,在下面的代码片段中省略了这些信息)并且不能创建比对应id的个数,
和
我还需要在没有其他对象引用它们时立即对对象进行 GC(即尽可能低地保持内存占用),所以我想使用 WeakReference
,而不是 SoftReference
的。
在下面的工厂方法示例中,T
不是通用类型——相反,它可以被认为是具有 id
字段的任意 class输入 String
,其中所有 ID 都是唯一的。每个值(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();
}
WeakReference
的主题在清除后需要进行 GC(即引用对象 GC)超出了这个问题的范围——在生产代码中,这个使用引用队列实现。
AtomicReference
仅用于从 lambda 外部返回值的目的(与工厂方法本身在同一线程中执行)。
现在,问题。
在 运行 代码成功运行几周后,我收到了一个 NPE
,它源自额外的 null
检查 IntelliJ IDEA 添加感谢 @NotNull
注释:
java.lang.IllegalStateException: @NotNull method com/example/T.from must not return null
实际上,这意味着 instanceRef
值没有在任何一个分支中设置,或者整个 computeIfPresent(...)
方法没有被调用。
我看到的竞争条件的唯一可能性是在 putIfAbsent(...)
和 computeIfPresent(...)
调用之间某处删除映射条目(从单独的线程处理引用队列到 GC 实例)。
我缺少的竞争条件是否有额外的空间?
您必须记住,不仅可以发生其他线程,还可以发生 GC。考虑这个片段:
instanceRef.set(oldInstance);
return ref;
});
// Here!!!!!
return instanceRef.get();
如果在 Here
点启动 GC,您认为会产生什么影响?
我怀疑你的错在@NotNull
因为这个方法可以returnnull
.
已添加 - 逻辑
如果最后的 instanceRef.get()
是 returning 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
之间删除了一个项目,就会发生这种情况。然而,找到一条 returns 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;
}
我需要一个对象的并发缓存,其中每个实例都包装一个唯一的 id
(可能还有一些额外的信息,为简单起见,在下面的代码片段中省略了这些信息)并且不能创建比对应id的个数,
和
我还需要在没有其他对象引用它们时立即对对象进行 GC(即尽可能低地保持内存占用),所以我想使用 WeakReference
,而不是 SoftReference
的。
在下面的工厂方法示例中,T
不是通用类型——相反,它可以被认为是具有 id
字段的任意 class输入 String
,其中所有 ID 都是唯一的。每个值(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();
}
WeakReference
的主题在清除后需要进行 GC(即引用对象 GC)超出了这个问题的范围——在生产代码中,这个使用引用队列实现。
AtomicReference
仅用于从 lambda 外部返回值的目的(与工厂方法本身在同一线程中执行)。
现在,问题。
在 运行 代码成功运行几周后,我收到了一个 NPE
,它源自额外的 null
检查 IntelliJ IDEA 添加感谢 @NotNull
注释:
java.lang.IllegalStateException: @NotNull method com/example/T.from must not return null
实际上,这意味着 instanceRef
值没有在任何一个分支中设置,或者整个 computeIfPresent(...)
方法没有被调用。
我看到的竞争条件的唯一可能性是在 putIfAbsent(...)
和 computeIfPresent(...)
调用之间某处删除映射条目(从单独的线程处理引用队列到 GC 实例)。
我缺少的竞争条件是否有额外的空间?
您必须记住,不仅可以发生其他线程,还可以发生 GC。考虑这个片段:
instanceRef.set(oldInstance);
return ref;
});
// Here!!!!!
return instanceRef.get();
如果在 Here
点启动 GC,您认为会产生什么影响?
我怀疑你的错在@NotNull
因为这个方法可以returnnull
.
已添加 - 逻辑
如果最后的 instanceRef.get()
是 returning 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
之间删除了一个项目,就会发生这种情况。然而,找到一条 returns 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;
}