【问题标题】:Is my getStatement method thread safe?我的 getStatement 方法线程安全吗?
【发布时间】:2016-11-24 21:51:03
【问题描述】:

我有一个下面的 Singleton 类,在我的 getStatement 方法中,我通过执行 if 检查来填充 CHM。

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

  private static class Holder {
    private static final CacheHolder INSTANCE = new CacheHolder();
  }

  public static CacheHolder getInstance() {
    return Holder.INSTANCE;
  }

  private CacheHolder() {}

  public BoundStatement getStatement(String cql) {
    Session session = TestUtils.getInstance().getSession();
    PreparedStatement ps = holder.get(cql);
    if (ps == null) {
      ps = session.prepare(cql);
      holder.put(cql, ps);
    }
    return ps.bind();
  }
}

我的getStatement 方法线程安全吗?

【问题讨论】:

  • 我建议使用Guava cache 而不是 CHM,因为它带有原子的“get-if-absent-compute”语义,这正是您想要的。
  • PreparedStatementBoundStatement 是什么?在 javadoc 中,我没有看到任何 bind 方法,所以它不是 java.sql.PreparedStatement。为什么这很重要?因为知道最终对象本身是否是线程安全的很重要。如果是,那么很好,但如果不是,那么你就有问题了,因为你确保创建它是线程安全的,但你的对象本身不是?嗯...不确定我更喜欢那个...
  • 我正在使用 Cassandra,所以我必须重新使用准备好的语句,这就是我在这里缓存它的原因。 Prepared StatementBoundStatement

标签: java multithreading thread-safety guava concurrenthashmap


【解决方案1】:

@javaguy 提供的答案是正确的,但只是一个小的优化,以确保同步块在不需要时不会为每个线程执行。

public static BoundStatement getStatement(String cql) {
    PreparedStatement ps = null;
    Session session = null;
    try {
      session = TestUtils.getInstance().getSession();
      PreparedStatement ps = holder.get(cql);
      if(ps == null) { // If PS is already present in cache, then we don't have to synchronize and make threads wait.
        synchronized {
          ps = holder.get(cql);
          if (ps == null) {
            ps = session.prepare(cql);
            holder.put(cql, ps);
          }
        }
      }
    } finally {
        //release the resources
    }
    return ps.bind();
  }

您也可以使用Guava Cache,或者如果您想要地图,则使用Guava MapMaker

使用 Guava 缓存:

LoadingCache<String, PreparedStatement> cache = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .build(
           new CacheLoader<String, PreparedStatement>() {
             public PreparedStatement load(String cql) throws Exception {
               return createPreparedStatement(cql);
             }
           });

使用地图制作工具:

ConcurrentMap<String, PreparedStatement> cache = new MapMaker()
       .concurrencyLevel(32)
       .weakValues()
       .makeComputingMap(
           new Function<String, PreparedStatement>() {
             public PreparedStatement apply(String cql) {
               return createPreparedStatement(cql);
             }
           });

另外,我建议不要缓存 PreparedStatement 的,因为这些资源需要释放 AFAIK。

【讨论】:

  • 这里用putIfAbsent方法代替同步块怎么样?
  • 不,不会有太大区别。 putIfAbsent 将确保当 2 个线程尝试写入相同的键时,只有一个值会继续执行,而尝试写入的另一个线程将无法插入并返回 thread1 放置的值。
【解决方案2】:

请参考@Bandi Kishore 的答案,因为它比下面的更有效(下面的答案需要synchronization 每次调用getStatement(),可以通过再添加一个null 检查来避免)。

我的 getStatement 方法线程安全吗?

不,它不是线程安全的,在您的 getStatement(String cql) 方法中,您正在使用竞争条件进行 null 检查,这通常称为 双重检查锁定 ,您可以在此查看here。即,当线程正在执行holder.get(cql); 时,您的代码中存在竞争条件,您需要synchronize 该代码的关键部分,如下所示:

public static BoundStatement getStatement(String cql) {
    PreparedStatement ps = null;
    Session session = null;
    try {
      session = TestUtils.getInstance().getSession();
      synchronized {
        PreparedStatement ps = holder.get(cql);
        if (ps == null) {
          ps = session.prepare(cql);
          holder.put(cql, ps);
       }
    }
    } finally {
        //release the resources
    }
    return ps.bind();
  }

另外,作为旁注,请确保您正在释放资源。

【讨论】:

  • 不,没有,基本上是有竞态条件,不是缓存问题(volatile只解决线程间的缓存问题)
猜你喜欢
  • 2013-03-31
  • 1970-01-01
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多