【发布时间】:2019-02-13 05:53:45
【问题描述】:
我有一个场景,我必须维护一个Map,它可以由多个线程填充,每个线程都修改它们各自的List(唯一标识符/键是线程名称),并且当一个线程的列表大小超过固定的批量大小,我们必须将记录持久化到数据库中。
聚合器类
private volatile ConcurrentHashMap<String, List<T>> instrumentMap = new ConcurrentHashMap<String, List<T>>();
private ReentrantLock lock ;
public void addAll(List<T> entityList, String threadName) {
try {
lock.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.unlock();
}
}
每 2 分钟后运行一个单独的线程(使用相同的锁)以持久保存 Map 中的所有记录(以确保每 2 分钟后保存一些内容并且地图大小不会变得太大)
if(//Some condition) {
Thread.sleep(//2 minutes);
aggregator.getLock().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().unlock();
}
}
这个解决方案在我们测试的几乎所有场景中都可以正常工作,除了有时我们会看到一些记录丢失了,即它们根本没有持久化,尽管它们被很好地添加到了地图中。
我的问题是:
- 这段代码有什么问题?
-
ConcurrentHashMap不是最好的解决方案吗? - 与
ConcurrentHashMap一起使用的List是否有问题? - 我应该在这里使用
ConcurrentHashMap的计算方法吗(我认为不需要,因为ReentrantLock已经在做同样的工作了)?
【问题讨论】:
-
不确定丢失的记录,但如果对
instrumentMap的所有访问都由lock保护,那么使用ConcurrentMap没有任何好处。 -
@Slaw 我同意我没有写这个,也不想改变这个,直到我理解代码的问题。谢谢你的回答
-
好吧,我无法在显示的代码中看到问题。虽然这并不意味着问题不存在,但适当的 minimal reproducible example 证明问题会有所帮助。要检查的一件事是发生了任何无人看管的访问。
recordSaver.persist是否曾经以非阻塞方式将列表传递给另一个线程?我问是因为您传递了List本身,而不是副本,这意味着非同步访问可能发生在某处。相比之下,您的每两分钟保存线程调用saver.persist并使用包含地图中所有展平值的“副本”。 -
@Slaw 您能否在答案中添加您的观察结果,以便我可以相信您是否有效:)
-
是否需要将仪器存储在地图中?持久性是通过仪器列表完成的,“threadName”键似乎未使用。
标签: java multithreading concurrency