在Map(多线程环境)中跟踪重复插入

时间:2016-09-15 12:18:14

标签: java multithreading duplicates spring-batch concurrenthashmap

我正在寻找一种方法来跟踪多线程environemnt中Map尝试相同密钥插入的次数,以便Map可以被多个读取和更新线程同时。如果无法轻松跟踪重复键插入尝试,则另一种解决方案是在重复键插入尝试的第一个迹象时终止应用程序。

以下用户定义的单例Spring bean显示了我的应用程序使用的全局缓存,该缓存使用多个分区的spring批处理作业加载(每个DataType要加载一个作业)。 addResultForDataType方法可以由多个线程同时调用。

public class JobResults {

    private Map<DataType, Map<String, Object>> results;

    public JobResults() {
        results = new ConcurrentHashMap<DataType, Map<String, Object>>();
    }

    public void addResultForDataType(DataType dataType, String uniqueId, Object result) {
        Map<String, Object> dataTypeMap = results.get(dataType);
        if (dataTypeMap == null) {
            synchronized (dataType) {
                dataTypeMap = results.get(dataType);
                if (dataTypeMap == null) {
                    dataTypeMap = new ConcurrentHashMap<String, Object>();
                    results.put(dataType, dataTypeMap);
                }
            }
        }
        dataTypeMap.put(uniqueId, result);
    }

    public Map<String, Object> getResultForDataType(DataType dataType) {
        return results.get(dataType);
    }

}

此处:

  • DataType可以被认为是表名或文件名 加载数据的位置。每个DataType指示一个表或文件。
  • uniqueId表示表或文件中每条记录的主键。
  • result是表示整行的对象。
  • 每条记录调用一次上述方法。在任何给定时间,多个线程可以插入相同DataType或不同DataType的记录。

我想创建另一个地图来跟踪重复的插入内容:

public class JobResults {

    private Map<DataType, Map<String, Object>> results;
    private Map<DataType, ConcurrentHashMap<String, Integer>> duplicates;

    public JobResults() {
        results = new ConcurrentHashMap<DataType, Map<String, Object>>();
        duplicates = new ConcurrentHashMap<DataType, ConcurrentHashMap<String, Integer>>();
    }

    public void addResultForDataType(DataType dataType, String uniqueId, Object result) {
        Map<String, Object> dataTypeMap = results.get(dataType);
        ConcurrentHashMap<String,Integer> duplicateCount = duplicates.get(dataType);
        if (dataTypeMap == null) {
            synchronized (dataType) {
                dataTypeMap = results.get(dataType);
                if (dataTypeMap == null) {
                    dataTypeMap = new ConcurrentHashMap<String, Object>();
                    duplicateCount = new ConcurrentHashMap<String, Integer>();
                    results.put(dataType, dataTypeMap);
                    duplicates.put(dataType, duplicateCount);
                }
            }
        }
        duplicateCount.putIfAbsent(uniqueId, 0);
        duplicateCount.put(uniqueId, duplicateCount.get(uniqueId)+1);//keep track of duplicate rows
        dataTypeMap.put(uniqueId, result);
    }

    public Map<String, Object> getResultForDataType(DataType dataType) {
        return results.get(dataType);
    }

}

我意识到statemet duplicateCount.put(uniqueId, duplicateCount.get(uniqueId)+1);不是隐式线程安全的。为了使其成为线程安全的,我需要使用同步来减慢插入速度。如何在不影响应用程序性能的情况下跟踪重复插入。如果保持跟踪重复插入并不容易,我可以在尝试覆盖地图中现有条目的第一个迹象时抛出异常。

注意我知道Map不允许重复密钥。我想要的是一种方法来跟踪任何此类尝试并暂停应用程序而不是覆盖Map中的条目。

2 个答案:

答案 0 :(得分:1)

尝试这样的事情:

    ConcurrentHashMap<String, AtomicInteger> duplicateCount = new ConcurrentHashMap<String, AtomicInteger>();

然后,当您准备增加计数时,请执行以下操作:

final AtomicInteger oldCount = duplicateCount.putIfAbsent(uniqueId, new AtomicInteger(1));
if (oldCount != null) {
    oldCount.incrementAndGet();
}

所以,如果你还没有在地图上计数,你会输1,如果你有,你将获得当前值并以原子方式递增它。这应该是线程安全的。

答案 1 :(得分:0)

如果要跟踪插入次数,可以将外部地图类型更改为Map<String, Pair<Integer, Object>>(或者,如果您不使用Apache Commons,只需Map<DataType, Map.Entry<Integer, InnerType>> ,其中Integer值是更新次数:

DataType key = ...;
Map<Integer, Object> value = ...;
dataTypeMap.compute(key, (k, current) -> {
    if (current == null) {
        /* Initial count is 0 */
        return Pair.of(0, value);
    } else {
        /* Increment count */
        return Pair.of(current.getFirst(), value);
    }));

如果你关心的是确保没有重复插入,你只需使用computeIfAbsent

DataType key = ...;
Map<Integer, Object> value = ...;
if (dataTypeMap.computeIfAbsent(key, k -> value)) != null) {
    /* There was already a value */
    throw new IllegalStateException(...);
});