我正在阅读旧的Brian Goetz article(2008),内容涉及在Servlet上下文中处理共享的可变数据时如何避免一些容易犯的错误。
他提到了Java内存模型(JMM)如何定义“保证读取变量的线程在一定条件下才能看到另一个线程中写入结果的条件”,以及“如何仅通过在以下位置进行同步来创建跨线程的先发生顺序”通用锁或访问通用volatile变量。” [重点是我的]
(而JMM定义了单个线程的“事前”排序是如何基于源代码中语句的排序的。)
本文继续介绍以下代码,作为处理会话数据的部分解决方案。
警告-Brian Goetz在其文章中提供的以下代码故意存在问题-请勿复制/使用它。之所以将其包含在此处,仅是因为Goetz在他的“事前发生”和可见性讨论中提到了它。我的问题是关于那个讨论的,而不是有关的代码:
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
PlayerScore result = new PlayerScore();
result.setName(hs.getName());
result.setScore(hs.getScore());
return result;
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");
if (newScore.getScore() > hs.getScore()) {
hs.setName(newScore.getName());
hs.setScore(newScore.getScore());
ctx.setAttribute("highScore", hs); // set-after-write!
}
}
上面代码末尾的“写后设置”行解决了一个常见问题:“可见性”问题,但不能解决其他与线程相关的问题(没有使用“ volatile”或“ synchronized”在上面的代码中。
我不明白这是如何解决“可见性”问题(即一个线程可能看到过时和/或不一致的数据版本的问题)。
本文介绍:
写后置位技术能够消除可见性问题,因为在排序之前传递是可传递的,并且在updateHighScore()中对setAttribute()的调用与对updateHighScore()中的getAttribute()的调用之间存在一个before-before边缘。 getHighScore()。由于对HighScore状态的更新发生在setAttribute()之前,而setAttribute()发生在getAttribute()的返回之前,而getAttribute()的返回发生在getHighScore()的调用者使用状态之前,所以传递性可以让我们得出结论,即getHighScore()至少与最近对setAttribute()的调用一样。
因此,我对以上内容的总结是:
hs.setName()和hs.setScore()发生在setAttribute()之前
updateHighScore()
setHigh()中的setAttribute()发生在返回之前
从getAttribute()
getAttribute()的返回发生在使用
由getHighScore()的调用者声明
因此:hs.setName()和hs.setScore()发生在
getHighScore()?
我在这里缺少一些概念上的东西。
陈述(2)如何成立?对于updateHighScore()中的单个线程,不是相反吗?
考虑到这是两个不同的方法,并且由两个不同的线程访问,并且没有显式使用同步或易变性,因此语句(3)的正确性如何,因此,不能保证线程之间的执行顺序/协调如何?
最佳答案
编辑/更新
至少可以说,我对这个问题的首次尝试尚不清楚。这是更新。
问题的核心:
Brian Goetz(以下称“ BG”)如何声明setAttribute()
中的updateHighScore()
在两个独立线程从getAttribute()
返回之前发生?
答案的核心:
因为BG的“事前准备”讨论中的隐含假设是ServletContext
是通过明智的方式实现的-提供了必要的可见性保证。
@Erwin和@Louis在他们的评论中建议这一点后,答案对我来说就到位了。
(脸部瞬间。)
这实际上就是问题的全部:答案简单明了-在事后看来。是不是总是这样?
补充笔记
问题不是代码本身,而是BG对代码的讨论。
尽管如此,请通篇阅读BG文章的结尾,以查看最可靠的线程安全代码示例。
我也试图澄清这个问题。
原始(未编辑)答案如下。它包含一些我认为仍然有用的附加信息。
如果我仍然错过重要的事情,请告诉我。我当然是在这里学习。
这是我自己的问题的部分答案(部分原因是那里可能会有更好/更清晰的答案)。
正如在该问题的一些评论中所指出的那样,答案确实是可以假定ServletContext
的实现可以实现某种形式的线程安全。两个示例:Tomcat使用ConcurrentHashMap类,而Jetty似乎使用包裹在ConcurrentHashMap
中的AtomicReference
。
另一个有助于我澄清这一点的参考点是Brian Goetz的“ Java Concurrency in Practice”中的以下部分:
线程安全库集合提供以下安全性
即使Javadoc不够清晰,发布保证
学科:
将键或值放在Hashtable,syncedMap或
Concurrent-Map安全地将其发布到检索的任何其他线程
从地图中获取(无论是直接获取还是通过迭代器获取)
AttributesMap具有更多上下文。
关于java - Java Servlet-共享的可变 session 数据,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59417027/