微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

惰性图结构缓存和并发

如何解决惰性图结构缓存和并发

我看到一些奇怪的NPE,发现这是一个并发问题。我正在使用类似于以下代码

class Manager {
  private final ConcurrentMap<String,Value> map = new ConcurrentHashMap<>();

  public Value get(String key) {
    Value v = map.get(key);
    if (v == null) {
      new Value(key,this);
      v = map.get(key);
    }
    return v;
  }

  public void doIt(String key) {
    get(key).doIt();
  }

  void register(String key,Value v) {
    map.put(key,v);
  }
}

class Value {
  private final Value[] values;
  private final SubValue v;

  Value(String key,Manager m) {
    m.register(key,this);

    // Initialize some values,this is where a cycle can be introduced
    // This is just some sample code,the gist is,we call Manager.get
    this.vs = new Value[]{ m.get("some-other-key") };
    // Other code ...

    this.v = new SubValue(m);
  }

  public void doIt() {
    this.v.doIt(); // <--- NPE here. v is null sometimes
  }
}

当我致电Manager.doIt时,有时会收到NPE,因为Value.vnull。据我了解事前发生的关系,有可能,当Manager.get被并发调用并且没有键的条目时,我可以取回一个尚未完全初始化的Value。 / p>

我正在Value的构造函数注册对象,因为Value对象之间的对象图可能具有循环,如果没有该循环,我会得到stackoverflow异常。

现在的问题是,如何确保在doItValue和所有连接的值都已完全初始化?我正在考虑在Manager.get中进行某种双重检查的锁定,但不确定如何最好地解决。像这样:

  public Value get(String key) {
    Value v = map.get(key);
    if (v == null) {
      synchronized(map) {
        v = map.get(key);
        if (v == null) {
          v = new Value(key,this);
        }
      }
    }
    return v;
  }

有人对如何解决这个问题有更好的主意吗?还是看到了该代码的并发问题?

解决方法

这里的问题是您正在使this在构造函数中转义。

class Value {
  private final Value[] values;
  private final SubValue v;

  Value(String key,Manager m) {
    m.register(key,this); <--- (this is not properly constructed)

    // Initialize some values,this is where a cycle can be introduced
    // This is just some sample code,the gist is,we call Manager.get
    this.vs = new Value[]{ m.get("some-other-key") };
    // Other code ...

    this.v = new SubValue(m);
  }

  public void doIt() {
    this.v.doIt(); // <--- NPE here. v is null sometimes
  }
}

现在,如果某些线程在映射中针对其对象构造不正确的键调用doIt,则可能会得到NPE,因为该对象的Subvalue v已经初始化。

该代码还有另一个问题。 Manager.get()是复合动作,应封装在synchronised块中。如果一个线程观察到某个键的null值,那么当它进入if块时,该观察可能会过时。由于映射包含在复合操作中,因此所有引用该映射的方法都应使用相同的锁保护-基本上,您需要使用相同的锁来保护get()register()

,

我所采用的解决方案如下,据我所知,它是可扩展且安全的:

class Manager {
  private final ConcurrentMap<String,Value> map = new ConcurrentHashMap<>();

  public Value get(String key) {
    Value v = map.get(key);
    if (v == null) {
      Map<String,Value> subMap = new HashMap<>();
      new Value(key,subMap);
      map.putAll(subMap);
      v = map.get(key);
    }
    return v;
  }

  public void doIt(String key) {
    get(key).doIt();
  }
}

class Value {
  private final Value[] values;
  private final SubValue v;

  Value(String key,Map<String,Value> subMap) {
    subMap.put(key,this);

    // Initialize some values,we call Manager.get
    this.vs = new Value[]{ subMap.containsKey("some-other-key") ? subMap.get("some-other-key") : m.get("some-other-key") };
    // Other code ...

    this.v = new SubValue(m);
  }

  public void doIt() {
    this.v.doIt();
  }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。