我需要延迟初始化地图及其内容。到目前为止,我有以下代码:
class SomeClass {
private Map<String, String> someMap = null;
public String getValue(String key) {
if (someMap == null) {
synchronized(someMap) {
someMap = new HashMap<String, String>();
// initialize the map contents by loading some data from the database.
// possible for the map to be empty after this.
}
}
return someMap.get(key); // the key might not exist even after initialization
}
}
这显然不是线程安全的,就好像一个线程在someMap
null时出现,继续将字段初始化为,new HashMap
并且当该字段仍将数据加载到映射中时,另一个线程执行a getValue
并且在可能存在的情况下不获取数据。
当第一次getValue
调用发生时,如何确保数据仅在地图中加载一次。
请注意,key
所有初始化后,地图中可能不会存在。同样,在所有初始化之后,映射可能只是空的。
双重检查锁定
双重检查锁定需要完成几个步骤才能正常工作,您错过了其中两个。
首先,您需要将其someMap
变成一个volatile
变量。这样一来,其他线程将在更改后但完成更改后看到更改。
private volatile Map<String, String> someMap = null;
您还需要null
对synchronized
块内进行第二次检查,以确保在等待进入同步区域时没有另一个线程为您初始化它。
if (someMap == null) {
synchronized(this) {
if (someMap == null) {
待使用前不要分配
在生成映射时,请在temp变量中构造它,然后在最后分配它。
Map<String, String> tmpMap = new HashMap<String, String>();
// initialize the map contents by loading some data from the database.
// possible for the map to be empty after this.
someMap = tmpMap;
}
}
}
return someMap.get(key);
解释为什么需要临时地图。完成这一行someMap = new HashMap...
后,该字段将someMap
不再为null。这意味着其他调用get
将看到它,并且从不尝试进入该synchronized
块。然后,他们将尝试从地图中获取数据,而无需等待数据库调用完成。
通过确保分配到someMap
同步块中的最后一步,可以防止这种情况的发生。
unmodifiableMap
正如评论中所讨论的,出于安全考虑,最好将结果保存为,unmodifiableMap
因为将来的修改将不是线程安全的。对于从未公开的私有变量,这不是严格要求的,但是对于将来仍然更安全,因为它将阻止人们稍后进入并更改代码而没有意识到。
someMap = Collections.unmodifiableMap(tmpMap);
为什么不使用ConcurrentMap?
ConcurrentMap
使单个操作(即putIfAbsent
)成为线程安全的,但它不满足此处的基本要求,即等到映射完全填充数据后才允许对其进行读取。
另外,在这种情况下,延迟初始化之后的Map不会再次被修改。这ConcurrentMap
将为在此特定用例中不需要同步的操作增加同步开销。
为什么要 对此进行 同步?
没有理由。:)这是提出此问题的有效答案的最简单方法。
在私有内部对象上进行同步肯定是更好的做法。您已经在改进封装方面进行了权衡,以稍微增加内存使用量和对象创建时间。同步的主要风险this
在于,它允许其他程序员访问您的锁对象,并可能尝试自己尝试对其进行同步。然后,这会导致他们的更新与您的更新之间发生不必要的争用,因此内部锁定对象会更安全。
实际上,尽管在许多情况下,单独的锁定对象是过大的。这是一个基于您的类的复杂性以及是否使用广泛而不是仅仅锁定on的简单性的判断调用this
。如有疑问,您可能应该使用内部锁定对象并采取最安全的路线。
在课堂里:
private final Object lock = new Object();
在方法中:
synchronized(lock) {
对于java.util.concurrent.locks
对象,在这种情况下它们不会添加任何有用的东西(尽管在其他情况下它们非常有用)。我们始终希望等到数据可用后,才能使用标准的同步块为我们提供所需的行为。
7.4.4 延迟初始化的bean 默认情况下,ApplicationContext实现在初始化过程中随即创建和配置所有单例bean。一般来说,这种预实例化是可取的,因为可以立即发现配置或周围环境中的错误,而不是在几个小时甚至几天以后。当这种行为不可取时,可以通过将bean定义标记为延迟初始化来阻止预实例化。延迟初始化的bean告诉IoC容器,当bean首次被请求时而不是在启动时创建一个实例。 在X
问题内容: 我正在使用静态代码块来初始化我拥有的注册表中的某些控制器。因此,我的问题是,我可以保证在首次加载该类时,该静态代码块仅被绝对调用一次吗?我知道我不能保证何时将调用此代码块,我猜是在Classloader首次加载时。我意识到我可以在静态代码块中的类上进行同步,但是我猜这实际上是怎么回事? 简单的代码示例将是; 还是我应该这样做? 问题答案: 是的,Java静态初始化器是线程安全的(使用第
问题内容: 我试图基于初始化按需持有人习惯用法创建一个线程安全的单例类。这是我的代码 我的期望是以线程安全的方式初始化ExecutorService,并且那里只有一个实例(静态)。 这段代码是实现了这一目标-还是需要任何更改? 问题答案: 根据SEI 指南,您的方法很好。 但是,由于我们有枚举,因此可以使用枚举的简单方法: 而且,如果您想变得真正聪明,还可以定义枚举实现的接口。因为这样您以后可以
我有maploader,它使用索引进行查询 > maploader 主 这个工作绝对精细的地图不会加载,直到我第一次接触地图。 但是当我为地图添加索引的时候,地图就会被加载,而不管是不是触摸地图。在Hazelcast文档中,MapStoreConfig类中的InitialLoadMode配置参数有两个值:LAZY和eager。如果InitialLoadMode设置为LAZY,则在映射创建期间不加载
问题内容: 我想延迟控制器的初始化,直到从服务器收到必要的数据为止。 我找到了针对Angular1.0.1的解决方案:延迟AngularJS路由更改,直到加载模型以防止闪烁,但无法使其与Angular1.1.0一起使用 模板 JavaScript http://jsfiddle.net/dTJ9N/1/ 问题答案: 由于$ http返回了promise,因此创建自己的deferred仅在htt
下面我分享了我的代码,我试图使用线程安全的Nashorn作为脚本引擎来评估简单的数学公式。公式将类似于“a*b/2”,其中a 我需要知道这种方法是否有助于使Nashorn线程在这个用例中安全。我在Attila的回答中读到,我们可以在线程之间共享脚本引擎对象,因为它们是线程安全的。 对于bindings和eval,因为我们正在为每次执行evaluate创建新线程,每个线程都有自己的bindings对