繁体   English   中英

将值放在ConcurrentHashMap中是否是原子的?

[英]Putting the value in ConcurrentHashMap is atomic or not?

我正在做一个与数据库建立连接的项目。 我需要查看exception is happening次数exception is happening如果有)。 我正在使用Multithreaded code ,这意味着多个线程将连接到数据库并插入数据库。 因此,在某些时候连接可能会丢失,因此我们需要查看这些异常发生了多少次。

因此,我编写了下面的代码,在catch块中,我捕获了异常,并在每次出现异常时都对增加计数进行计数并将其放入ConcurrentHashMap

class Task implements Runnable {

     public static final AtomicInteger counter_sql_exception = new AtomicInteger(0);
     public static final AtomicInteger counter_exception = new AtomicInteger(0);
     public static ConcurrentHashMap<String, Integer> exceptionMap = new ConcurrentHashMap<String, Integer>();

     @Override
     public void run() {

     try {

         //Make a db connection and then executing the SQL-

         } catch (SQLException e) {
              synchronized(this) {
                   exceptionMap.put(e.getCause().toString(), counter_sql_exception.incrementAndGet());
              }
              LOG.Error("Log Exception")
          } catch (Exception e) {
              synchronized(this) {
                   exceptionMap.put(e.getCause().toString(), counter_exception.incrementAndGet());
              }
              LOG.Error("Log Exception")
        }
      }
  }

我的问题是-今天我进行了代码审查,我的一位高级团队成员说,您将不需要在catch blockexceptionMapsynchronized(this) 我说是的,因为计数器的增加是原子的,所以我们将需要它。 在地图中放置一个新值是原子的。 但是,在没有同步的情况下进行这两者并不是原子的。 他说ConurrentHashMap将为您做到这一点。

那么我是否需要在该exceptionMap上使用exceptionMap synchronized(this)块? 如果没有,那为什么呢? 如果是,那我应该引用什么理由。

如果要统计每个异常的发生次数,则需要这样的操作:

private static final ConcurrentMap<String, AtomicInteger> exceptionMap = new ConcurrentHashMap<String, AtomicInteger>();

private static void addException(String cause) {
  AtomicInteger count = exceptionMap.get(cause);
  if(count == null) {
    count = new AtomicInteger();
    AtomicInteger curCount = exception.putIfAbsent(cause, count);
    if(curCount != null) {
      count = curCount;
    }
  }
  count.incrementAndGet();
}

请注意,除非定期清除,否则具有静态的异常映射是资源泄漏。

如@dnault所述,您还可以使用番石榴的AtomicLongMap

更新:对您的原件的一些评论:

  • 您是正确的,您确实需要另一个包装同步块来确保最新值确实进入了映射。 但是,正如@Perception在注释中已经指出的那样,您正在错误的对象实例上进行同步(由于要更新静态映射,因此需要一个静态实例,例如Task.class )。
  • 但是,您使用的是静态计数器,但是String键对于不同的异常可能会有所不同,因此您实际上并没有在计算每个异常的原因,而是将随机数作为各种映射值插入
  • 最后,如我的示例所示,可以通过适当地使用ConcurrentMap来解决上述问题并完全丢弃同步块。

而且这种方式也应该起作用。

private static final ConcurrentMap<String, Integer> exceptionMap = new ConcurrentHashMap<String, Integer>();

private static void addException(String cause) {
    Integer oldVal, newVal;
    do {
      oldVal = exceptionMap .get(cause);
      newVal = (oldVal == null) ? 1 : (oldVal + 1);
    } while (!queryCounts.replace(q, oldVal, newVal)); 
}

ConcurrentHashMap不允许使用null值,因此如果使用oldValue == null调用,则replace将引发异常。 我使用此代码通过返回oldValue增加计数器的delta

private final ConcurrentMap<Integer,Integer> counters = new ConcurrentHashMap<Integer,Integer>();

private Integer counterAddDeltaAndGet(Integer key, Integer delta) {
    Integer oldValue = counters.putIfAbsent(key, delta);
    if(oldValue == null) return null;

    while(!counters.replace(key, oldValue, oldValue + delta)) {
        oldValue = counters.get(key);
    }

    return oldValue;
}

您不必使用synchronized块和AtomicInteger 您可以使用ConcurrentHashMap.compute方法来执行此操作,该方法是线程安全的原子操作。 所以你的代码看起来像这样

public class Task implements Runnable {

    public static final Map<String, Integer> EXCEPTION_MAP = new ConcurrentHashMap<String, Integer>();

    @Override
    public void run() {
        try {

            // Make a db connection and then executing the SQL-

        } catch (Exception e) {
            EXCEPTION_MAP.compute(e.getCause().toString(), (key, value) -> {
                if (value == null) {
                    return 1;
                }
                return ++value;
            });
        }

    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM