【发布时间】:2018-05-23 18:27:41
【问题描述】:
在我的应用程序中,我需要延迟设置一个变量,因为在类初始化期间我无法访问必要的方法,但我还需要可以跨多个线程访问该值。我知道我可以使用double-checked locking 来解决这个问题,但这似乎有点矫枉过正。 我需要调用的获取值的方法是幂等的,返回值永远不会改变。我想像在单线程环境中一样懒惰地初始化引用。看起来这应该可以工作,因为对引用的读写是原子的。[1][2]
这是我正在做的一些示例代码。
// views should only be accessed in getViews() since it is
// lazily initialized. Call getViews() to get the value of views.
private List<String> views;
/* ... */
private List<String> getViews(ServletContext servletContext) {
List<String> views = this.views;
if (views == null) {
// Servlet Context context and init parameters cannot change after
// ServletContext initialization:
// https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html#setInitParameter(java.lang.String,%20java.lang.String)
String viewsListString = servletContext.getInitParameter(
"my.views.list.VIEWS_LIST");
views = ListUtil.toUnmodifiableList(viewsListString);
this.views = views;
}
return views;
}
This question about 32-bit primitives is similar,但我想确认对 Strings 和 Lists 等对象的引用行为是相同的。
看起来这应该可以正常工作,因为每个线程要么会看到null 并重新计算值(这不是问题,因为值永远不会改变)或看到已经计算的值。我在这里错过任何陷阱吗?这段代码是线程安全的吗?
【问题讨论】:
-
如果没有同步(同步块或易失性),您最终可能会导致每个线程都有自己的列表实例(每个线程都可以看到
views == null并初始化变量并使用自己的列表副本) -
对引用和原语的访问是原子的。 Java 5.0 解决了对 64 位值的原子访问问题
-
在此实现下,每个线程可能会以不同的
views实例结束。可以吗? -
@erickson,啊,我并没有真正考虑过,从技术上讲,所有这些实例都将包含等效数据,但我想这可能会导致潜在的内存问题。为了这个问题,让我们假设每个线程都获得它自己的实例是可以的,但我很高兴考虑到这个陷阱。谢谢!
-
@stiemannkj1 字符串的散列是一个惰性初始化的 32 位值,没有易变的语义,因为赛车计算具有相同的结果。然而,除非隔离到极其安全、性能关键的代码中,否则通常不值得这样的技巧。
标签: java multithreading concurrency volatile