我的 getStatement 方法线程安全吗?

Is my getStatement method thread safe?

我有一个下面的 Singleton class,在我的 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 方法线程安全吗?

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

Is my getStatement method thread safe?

不,它不是线程安全的,在您的 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();
  }

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

@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 or if you want a Map then 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。