在睡了一晚之后,我想出了几个方法。两者都不是特别优雅,但让我把它们放在这里以供思考:
MemCache 确实提供(至少)4 个命令,这些命令以某种方式自动修改条目并返回有关其先前状态的一些信息(@Moshe Shaham 也指出了其中两个):
-
delete https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#delete%28java.lang.Object%29: 删除条目并返回该条目是否存在
-
增量 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#increment%28java.lang.Object,%20long%29: 增加一个条目并返回其新值
-
put https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#put%28java.lang.Object,%20java.lang.Object,%20com.google.appengine.api.memcache.Expiration,%20com.google.appengine.api.memcache.MemcacheService.SetPolicy%29: 放置一个条目并返回它是否存在(如果与右侧一起使用)policy https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService.SetPolicy)
-
putIfUntouched https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#putIfUntouched%28java.lang.Object,%20com.google.appengine.api.memcache.MemcacheService.IdentifiableValue,%20java.lang.Object%29: 如果条目仍然与预期状态匹配则放置一个条目(返回是否匹配)
让我为它们每个提供一个可能的实现。前 3 个特定于整数键,并且要求实际条目具有正键(因为它们将使用相应的负键作为标记条目)。
Note:下面给出的示例本质上是概念性的。其中一些可能仍然允许竞争条件,但我实际上还没有测试过它们中的任何一个。所以,对它们持保留态度。
开始:
1.删除
首次存储条目时,将标记与其一起存储(以及派生的相应密钥)。根据尝试删除此标记的成功来控制获取和删除。
public void putForAtomicGnD(MemcacheService memcache, int key, Object value) {
assert key>=0;
memcache.put(key, value); // Put entry
memcache.put(-key-1, null); // Put a marker
}
public Object atomicGetAndDelete(MemcacheService memcache, int key) {
assert key>=0;
if (!memcache.delete(-key-1)) return null; // Are we first to request it?
Object result = memcache.get(key); // Grab content
memcache.delete(key); // Delete entry
return result;
}
可能的竞争条件: putForAtomicGnD 可能会覆盖正在读取的值。可以通过使用来避免ADD_ONLY_IF_NOT_PRESENT https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService.SetPolicy#ADD_ONLY_IF_NOT_PRESENT看跌期权和毫秒无重新添加 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#deleteAll%28java.util.Collection,%20long%29用于删除。
可能的优化:使用putAll https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#putAll%28java.util.Map%29.
2.增量
有一个与控制获取和删除的每个条目相关联的读取计数器。
public Object atomicGetAndDelete(MemcacheService memcache, int key) {
assert key>=0;
if (memcache.increment(-key-1, 1L, 0L) != 1L) return null; // Are we 1st?
Object result = memcache.get(key); // Grab content
memcache.delete(key); // Delete entry
return result;
}
注意:如此处所示,此代码仅允许每个密钥使用一次。要重新使用,需要删除相应的标记,这会导致竞争条件(再次可以通过 millisNoReAdd 解决)。
3. put
具有与门控获取和删除的每个条目相关联的读取标志(不存在标记条目)。本质上与“1.删除”方法相反。
public Object atomicGetAndDelete(MemcacheService memcache, int key) {
assert key>=0;
if (!memcache.put(-key-1, null, null, SetPolicy.ADD_ONLY_IF_NOT_PRESENT))
return null; // Are we 1st?
Object result = memcache.get(key); // Grab content
memcache.delete(key); // Delete entry
memcache.delete(-key-1, 10000); // Delete marker with millisNoReAdd
return result;
}
可能的竞争条件:另一个 put 可能会覆盖正在读取的值。同样,可以使用 millisNoReAdd 来解决。
可能的优化:使用删除所有 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#deleteAll%28java.util.Collection,%20long%29.
4. putIfUntouched
我目前最喜欢的:尝试获取一个条目并将其设置为null在模拟交易中并使用该交易的成功来控制删除。
public Object atomicGetAndDelete(MemcacheService memcache, Object key) {
IdentifiableValue result = memcache.getIdentifiable(key);
if (result==null) return null; // Entry didn't exist
if (result.getValue()==null) return null; // Someone else got it
if (!memcache.putIfUntouched(key, result, null)) return null; // Still 1st?
memcache.delete(key); // Delete entry
return result.getValue();
}
注意:这种方法几乎是完全通用的(对键类型没有限制),因为它不需要能够从给定的标记对象派生出标记对象的键。它唯一的限制是它不支持实际的null-entries,因为该值被保留以表示“条目已被占用”。
可能的竞争条件?我目前没有看到任何内容,但也许我错过了一些东西?
可能的优化: putIfUntouched 与立即到期日 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/Expiration (在这里了解如何! https://stackoverflow.com/questions/23478588/appengine-memcache-expiration-policies/23479048#23479048) 而不是删除。
结论
似乎有很多方法可以做到这一点,但到目前为止我想出的方法看起来都不是特别优雅。请使用此处给出的示例主要作为思考的基础,让我们看看是否可以提出一个更干净的解决方案,或者至少让自己相信上面的其中一个(putIfUntouched?)实际上会可靠地工作。