以下代码是否安全线程

时间:2018-08-07 06:30:43

标签: java multithreading concurrency hashmap concurrenthashmap

我有一个场景,我必须维护一个可以由多个线程填充的Map,每个Map都要在那里修改各自的List(唯一的标识符/键是线程名),并且当线程的列表大小超过固定的批处理大小时,我们必须将记录保留在数据库中。

下面的示例代码:

private volatile ConcurrentHashMap<String, List<T>>  instrumentMap = new ConcurrentHashMap<String, List<T>>();
private ReadWriteLock lock ;

public void addAll(List<T> entityList, String threadName) {
    try {
        lock.readLock().lock();
        List<T> instrumentList = instrumentMap.get(threadName);
        if(instrumentList == null) {
            instrumentList = new ArrayList<T>(batchSize);
            instrumentMap.put(threadName, instrumentList);
        }

        if(instrumentList.size() >= batchSize -1){
            instrumentList.addAll(entityList);
            recordSaver.persist(instrumentList); 
            instrumentList.clear();
        } else {
            instrumentList.addAll(entityList);  
        }
    } finally {
        lock.readLock().unlock();
    }

}

每2分钟有一个单独的线程在运行,以持久保存Map中的所有记录(以确保每2分钟就有一个持久的对象,并且map的大小不会太大),并且在启动时会阻塞其他所有线程线程(检查readLock和writeLock美国,其中writeLock具有更高的优先级)

if(//Some condition) {
                    Thread.sleep(//2 minutes);
                    aggregator.getLock().writeLock().lock();
                    List<T> instrumentList = instrumentMap .values().stream().flatMap(x->x.stream()).collect(Collectors.toList());
                    if(instrumentList.size() > 0) {

                        saver.persist(instrumentList);
                        instrumentMap .values().parallelStream().forEach(x -> x.clear());
                    aggregator.getLock().writeLock().unlock();
                }

该解决方案几乎可以在我们测试的每种情况下都可以正常工作,除了有时我们会看到某些记录丢失了,即尽管在Map中添加了很好的记录也根本没有保留

我的问题是这段代码有什么问题? ConcurrentHashMap不是这里的最佳解决方案吗? 这里使用读/写锁是否有问题? 我应该进行顺序处理吗?

1 个答案:

答案 0 :(得分:6)

不,它不是线程安全的。

问题是您正在使用ReadWriteLock的 read 锁。这不保证可以独占访问进行更新。为此,您需要使用 write 锁。

但是您根本不需要使用单独的锁。您可以简单地使用ConcurrentHashMap.compute方法:

instrumentMap.compute(threadName, (tn, instrumentList) -> {
  if (instrumentList == null) {
    instrumentList = new ArrayList<>();
  }

  if(instrumentList.size() >= batchSize -1) {
    instrumentList.addAll(entityList); 
    recordSaver.persist(instrumentList); 
    instrumentList.clear();
  } else {
    instrumentList.addAll(entityList);
  }

  return instrumentList;
});

这使您可以更新列表中的项目,同时还可以保证对给定密钥的列表具有独占访问权限。

我怀疑您可以将compute调用拆分为computeIfAbsent(如果列表不存在,则添加列表),然后再拆分computeIfPresent(更新/持久列表):这两个操作的原子性在这里不是必需的。但是将它们拆分没有实际意义。


此外,instrumentMap几乎可以确定是不可变的。除非您真的想重新分配其值(考虑到这段代码,我对此表示怀疑),否则请删除volatile并将其定为最终值。

类似地,非最终锁也有问题。如果您坚持使用锁,那么也要最后确定。