散列映射中对象的这种延迟初始化模式是否是线程安全的?

mar*_*hon 2 java concurrency multithreading locking hashmap

我想尽可能避免锁定读取.但这种"感觉"就像是双重检查锁定,即使没有涉及部分初始化的成员.

这是一个很好的结构吗?

private final Map<String, Stuff> stash = new HashMap<String, Stuff>();

public Stuff getStuff(String name) {

    if (stash.containsKey(name))
        return stash.get(name);

    synchronized(stash) {
        if (stash.containsKey(name)) {
            return stash.get(name);
        }
        else {
            Stuff stuff = StuffFactory.create(name);
            stash.put(name, stuff);
            return stuff;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Rol*_*lig 6

不,这个结构不是线程安全的.

假设线程writer正在将某些东西放入地图中,并且必须调整太小的地图.这是在synchronized街区内完成的,所以你可能认为你很好.

在调整大小期间,地图中没有任何内容可以保证.

现在,在同一时间,假设一个线程reader调用getStuff现有元素.这个线程可以直接访问地图,因为它没有synchronized在第一次调用containsKey和时遇到阻塞get.它将找到未定义状态的映射,虽然它只读取,但它访问内容未定义的数据.可能的结果包括:

  • getStuffnull它不应该返回.
  • getStuff返回预期的Stuff.
  • getStuff返回HashMap调整大小期间实现使用的一些内部对象.
  • getStuff返回一些Stuff与名称无关的其他内容.
  • getStuff 陷入无限循环.

这只是一个应该易于理解的明显案例.所以不,当有像ConcurrentHashMapKuava 这样精心设计的类时,不要采取捷径MapMaker.

顺便说一句:containsKey首先调用然后get使用相同的密钥是相当低效的.只需打电话get,保存结果并将其与之比较null.您将在地图中保存一个搜索操作.