【问题标题】:Replace use of "get/check/put" with putIfAbsent将“get/check/put”的使用替换为 putIfAbsent
【发布时间】:2017-01-01 18:53:19
【问题描述】:

我正在使用 Cassandra 并使用 Datastax Java 驱动程序。我正在尝试通过缓存来重用准备好的语句。

  private static final Map<String, PreparedStatement> holder = new ConcurrentHashMap<>();

  public BoundStatement getStatement(String cql) {
    Session session = TestUtils.getInstance().getSession();
    PreparedStatement ps = holder.get(cql);
    // no statement is cached, create one and cache it now.
    if (ps == null) {
      synchronized (this) {
        ps = holder.get(cql);
        if (ps == null) {
          ps = session.prepare(cql);
          holder.put(cql, ps);
        }
      }
    }
    return ps.bind();
  }

我上面的getStatement 方法将被多个线程调用,所以我必须确保它是线程安全的。我正在使用 Java 7,所以很遗憾不能使用 computeIfAbsent

当我针对静态分析工具运行我的代码时,它给了我一个小警告,这让我想有没有更好的方法在 Java 7 中编写上述代码?

Might be better to replace use of get/check/put with putIfAbsent

更新:

  public BoundStatement getStatement(String cql) {
    Session session = TestUtils.getInstance().getSession();
    PreparedStatement ps = holder.get(cql);
    // no statement is cached, create one and cache it now.
    if (ps == null) {
      ps = session.prepare(cql);
      PreparedStatement old = holder.putIfAbsent(cql, ps);
      if (old!=null)
        ps=old;
    }
    return ps.bind();
  }

【问题讨论】:

  • 这是错误的,原因有很多。一方面,您通过在实例上同步来保护静态字段。另一方面,您正在混合同步和无锁集合。
  • 您的方法假设值得缓存准备好的语句实例;是吗?每次构建新实例的实际性能损失是多少? (真正的问题;我很少与他们打交道,所以不知道他们有多么重量级)。
  • 如果我不缓存这些准备好的语句,那么我的所有日​​志都会被来自 datastax java 驱动程序的警告消息Re-preparing already prepared query . Please note that preparing the same query more than once is generally an anti-pattern and will likely affect performance. Consider preparing the statement only once. 填满。这是question,它对此进行了更多讨论,因此我决定重用准备好的语句。

标签: java multithreading thread-safety concurrenthashmap


【解决方案1】:

你拥有它的方式并不算太糟糕,除了一个线程可以阻塞另一个线程,即使他们没有尝试做出相同的准备好的语句。

在 Java 8 中使用 computeIfAbsent 确实会好得多。在 Java 7 中,您可以这样做:

ps = holder.get(cql);
if (ps == null) {
  ps = session.prepare(cql);
  PreparedStatement old = holder.putIfAbsent(cql, ps);
  if (old!=null)
    ps=old;
}

如果两个线程同时尝试创建同一个线程,您偶尔会创建一个不必要的 PreparedStatement,但在最坏的情况下,这相当于不使用缓存。

或者,如果您可以使用 guava 库,那么 guava LoadingCache 完全可以满足您的需求:https://google.github.io/guava/releases/16.0/api/docs/com/google/common/cache/CacheBuilder.html

【讨论】:

  • 我现在是否还需要 synchronized 阻止,或者我可以摆脱它,只使用您对 putIfAbsent 的建议?
  • 你不需要同步块
  • 我已经用代码更新了问题。你的意思是这样的吧?只要确保我做对了。
  • 是的,就像那样...但是我写了一个错误,你复制了它。我修好了,所以你应该用同样的方法来修。
  • 是的,再次编辑它。当我开始使用它时,我注意到了这一点。让我知道这次我做对了吗?
猜你喜欢
  • 1970-01-01
  • 2018-01-09
  • 2015-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-26
  • 1970-01-01
相关资源
最近更新 更多