2.2.2 示例:延迟初始化中的竞态条件

使用“先检查后执行”的一种常见情况就是延迟初始化。延迟初始化的目的是将对象的初始化操作推迟到实际被使用时才进行,同时要确保只被初始化一次。在程序清单2-3中的LazyInitRace说明了这种延迟初始化情况。getInstance方法首先判断ExpensiveObject是否已经被初始化,如果已经初始化则返回现有的实例,否则,它将创建一个新的实例,并返回一个引用,从而在后来的调用中就无须再执行这段高开销的代码路径。

程序清单2-3 延迟初始化中的竞态条件(不要这么做)

@NotThreadSafe

public class LazyInitRace{

private ExpensiveObject instance=null;

public ExpensiveObject getInstance(){

if(instance==null)

instance=new ExpensiveObject();

return instance;

}

}

在LazyInitRace中包含了一个竞态条件,它可能会破坏这个类的正确性。假定线程A和线程B同时执行getInstance。A看到instance为空,因而创建一个新的ExpensiveObject实例。B同样需要判断instance是否为空。此时的instance是否为空,要取决于不可预测的时序,包括线程的调度方式,以及A需要花多长时间来初始化ExpensiveObject并设置instance。如果当B检查时,instance为空,那么在两次调用getInstance时可能会得到不同的结果,即使getInstance通常被认为是返回相同的实例。

在UnsafeCountingFactorizer的统计命中计数操作中存在另一种竞态条件。在“读取-修改-写入”这种操作(例如递增一个计数器)中,基于对象之前的状态来定义对象状态的转换。要递增一个计数器,你必须知道它之前的值,并确保在执行更新的过程中没有其他线程会修改或使用这个值。 ...

Get Java并发编程实战 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.