Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 下面的代码是线程安全的吗_Java_Multithreading_Concurrency_Hashmap_Concurrenthashmap - Fatal编程技术网

Java 下面的代码是线程安全的吗

Java 下面的代码是线程安全的吗,java,multithreading,concurrency,hashmap,concurrenthashmap,Java,Multithreading,Concurrency,Hashmap,Concurrenthashmap,我有一个场景,我必须维护一个可以由多个线程填充的映射,每个线程修改各自的列表(唯一标识符/键是线程名),当线程的列表大小超过固定的批处理大小时,我们必须在DB中持久化记录 示例代码如下: private volatile ConcurrentHashMap<String, List<T>> instrumentMap = new ConcurrentHashMap<String, List<T>>(); private ReadWriteLock

我有一个场景,我必须维护一个可以由多个线程填充的映射,每个线程修改各自的列表(唯一标识符/键是线程名),当线程的列表大小超过固定的批处理大小时,我们必须在DB中持久化记录

示例代码如下:

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();
    }

}
private volatile ConcurrentHashMap instrumentMap=new ConcurrentHashMap();
私有读写锁;
public void addAll(列表entityList,字符串threadName){
试一试{
lock.readLock().lock();
List-instrumentList=instrumentMap.get(threadName);
如果(instrumentList==null){
instrumentList=新的ArrayList(batchSize);
instrumentMap.put(线程名,instrumentList);
}
if(instrumentList.size()>=batchSize-1){
instrumentList.addAll(entityList);
recordSaver.persist(instrumentList);
instrumentList.clear();
}否则{
instrumentList.addAll(entityList);
}
}最后{
lock.readLock().unlock();
}
}
每隔2分钟会有一个单独的线程运行,以持久化映射中的所有记录(以确保每2分钟后持久化一些内容,并且映射大小不会变得太大),当它启动时,会阻止所有其他线程(检查readLock和writeLock用法,其中writeLock具有更高的优先级)

if(//某些条件){
Thread.sleep(//2分钟);
聚合器.getLock().writeLock().lock();
List instrumentList=instrumentMap.values().stream().flatMap(x->x.stream()).collect(collector.toList());
如果(instrumentList.size()>0){
saver.persist(instrumentList);
instrumentMap.values().parallelStream().forEach(x->x.clear());
聚合器.getLock().writeLock().unlock();
}
此解决方案几乎适用于我们测试的每个场景,但有时我们会看到一些记录丢失,即尽管它们在Map中添加得很好,但根本没有持久化

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

不,它不是线程安全的

问题是您使用的是ReadWriteLock的读锁。这并不保证进行更新的独占访问。您需要使用写锁

但实际上根本不需要使用单独的锁。只需使用
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;
});
instrumentMap.compute(threadName,(tn,instrumentList)->{
如果(instrumentList==null){
instrumentList=新的ArrayList();
}
if(instrumentList.size()>=batchSize-1){
instrumentList.addAll(entityList);
recordSaver.persist(instrumentList);
instrumentList.clear();
}否则{
instrumentList.addAll(entityList);
}
返回列表;
});
这允许您更新列表中的项目,同时还保证对给定密钥的列表的独占访问

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


此外,
instrumentMap
几乎肯定不应该是volatile。除非您真的想重新分配它的值(鉴于此代码,我对此表示怀疑),否则请删除volatile并将其设置为final


同样,非最终锁也是有问题的。如果你坚持使用一个锁,也要使它成为最终锁。

等等,你是在读锁下保存东西吗?你认为写锁可能是正确的吗?如果你使用锁和并发哈希映射,你经常会出错。@Andy Turner读有什么问题吗当每个线程修改/持久化其各自的列表时锁定?那么您的建议是什么?我应该只使用写锁定吗?我不能使用普通哈希映射here@AndyTurner您建议的解决方案很好。但是,正如我所提到的,还有一个问题,即“每2分钟运行一个单独的线程”,现在这个线程是使用writeLock,这肯定不同于原子操作计算(在计算中使用锁)。您认为我应该公平地使用write lock吗?感谢您的帮助,我将尝试此方法。“instrumentList几乎肯定不应该是易变的”您是指此处的instrumentMap?@Amit yes.Typo。如果重新映射函数被调用两次,此代码是否会导致问题?来自Javadoc:“
当多个线程尝试更新(包括可能多次调用重新映射函数)时,默认实现可能会重试这些步骤。
”事实上,在compute中调用它并不能阻止两个线程同时添加到同一个
instrumentList
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;
});