双重检查锁定
双重检查锁定需要完成几个步骤才能正常工作,您错过了其中两个。
首先,您需要将其
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 = newHashMap...后,该字段将
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对象,在这种情况下它们不会添加任何有用的东西(尽管在其他情况下它们非常有用)。我们始终希望等到数据可用后,才能使用标准的同步块为我们提供所需的行为。



