我正在阅读官方GAE documentation on transactions,我无法理解何时抛出ConcurrentModificationException
。
看看我在这里复制粘贴的一个例子:
int retries = 3;
while (true) {
Transaction txn = datastore.beginTransaction();
try {
Key boardKey = KeyFactory.createKey("MessageBoard", boardName);
Entity messageBoard = datastore.get(boardKey);
long count = (Long) messageBoard.getProperty("count");
++count;
messageBoard.setProperty("count", count);
datastore.put(messageBoard);
txn.commit();
break;
} catch (ConcurrentModificationException e) {
if (retries == 0) {
throw e;
}
// Allow retry to occur
--retries;
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
现在,对数据存储区(在此示例中)的所有写入都包含在事务中。那么为什么会抛出ConcurrentModificationException
?
如果某个未包含在事务中的其他代码更新了上述代码正在修改的同一实体,是否会发生这种情况?如果我确保更新实体的所有代码始终包含在事务中,是否可以保证我不会获得ConcurrentModificationException
?
答案 0 :(得分:1)
我在GAE邮件列表中找到了答案。
我对交易在GAE中的运作方式存在误解。我曾经想过,在事务提交之前,开始事务将锁定对数据存储区的任何并发更新。那将是一场表演的噩梦,因为所有更新都会阻止这笔交易,我很高兴事实并非如此。
相反,发生的情况是,第一次更新获胜,如果在后续更新中检测到冲突,则抛出异常。
这首先使我感到惊讶,因为这意味着许多交易需要重试逻辑。但它看起来类似于PostgreSQL语义中的“可序列化隔离”级别,但在PostgreSQL中你也可以选择锁定单个行和列。
答案 1 :(得分:0)
您似乎正在做他们建议您不应该做的事情:http://code.google.com/appengine/docs/java/datastore/transactions.html#Uses_for_Transactions
<击>的警告!上面的示例仅为了简单起见,以交易方式递增计数器。如果您的应用程序具有经常更新的计数器,则不应以事务方式增加它们,甚至不应在单个实体内增加它们。使用计数器的最佳做法是使用称为反分片的技术。
也许上面的警告不适用,但之后的内容似乎暗示了你所看到的问题:
这需要一个事务,因为在此代码获取对象之后,但在保存修改后的对象之前,该值可能会被另一个用户更新。如果没有事务,用户的请求将在其他用户更新之前使用count值,并且save会覆盖新值。通过事务,应用程序将被告知其他用户的更新。如果在交易期间更新了实体,则交易将失败并显示
ConcurrentModificationException
。应用程序可以重复事务以使用新数据。
换句话说:似乎有人在不使用交易的情况下修改您的实体,同时您正在使用交易更新同一实体。
注意:在极少数情况下,即使事务返回超时或内部错误异常,事务也会完全提交。出于这个原因,最好尽可能使交易具有幂等性。
公平警告:我对图书馆不熟悉,但上述引文来自显示示例交易的文档(这与您在原始问题中发布的内容相同)。