【发布时间】:2017-08-05 16:06:27
【问题描述】:
我有一个下面的类,其中我有一个add 方法,该方法由另一个线程调用以填充我的clientidToTimestampHolder 多图。然后在下面的同一个类中,我启动了一个每 60 秒运行一次的后台线程,并调用 processData() 方法,该方法迭代同一个地图并将所有这些数据发送到其他一些服务。
public class Handler {
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final Multimap<String, Long> clientidToTimestampHolder = ArrayListMultimap.create();
private static class Holder {
private static final Handler INSTANCE = new Handler();
}
public static Handler getInstance() {
return Holder.INSTANCE;
}
private Handler() {
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
processData();
}
}, 0, 60, TimeUnit.SECONDS);
}
// called by another thread to populate clientidToTimestampHolder map
public void add(final String clientid, final Long timestamp) {
clientidToTimestampHolder.put(clientid, timestamp);
}
// called by background thread
public void processData() {
for (Entry<String, Collection<Long>> entry : clientidToTimestampHolder.asMap().entrySet()) {
String clientid = entry.getKey();
Collection<Long> timestamps = entry.getValue();
for (long timestamp : timestamps) {
boolean isUpdated = isUpdatedClient(clientid, timestamp);
if (!isUpdated) {
updateClient(String.valueOf(clientid));
}
}
}
}
}
我的问题是,add 方法每次都会被不同的线程调用。那么我是否需要创建clientidToTimestampHolder 映射的副本并将该副本作为参数传递给processData() 方法,而不是直接处理该映射?
因为现在我使用同一个映射来填充其中的数据,然后还迭代同一个映射以将内容发送到其他服务,所以我不会从该映射中删除数据,因此这些条目将始终存在于该映射中.
解决此问题的最佳方法是什么?而且我需要确保它是线程安全的并且没有竞争条件,因为我不能丢失任何clientid。
更新
所以我的processData 方法会是这个样子?
public void processData() {
synchronized (clientidToTimestampHolder) {
Iterator<Map.Entry<String, Long>> i = clientidToTimestampHolder.entries().iterator();
while (i.hasNext()) {
String clientid = i.next().getKey();
long timestamp = i.next().getValue();
boolean isUpdated = isUpdatedClient(clientid, timestamp);
if (!isUpdated) {
updateClient(clientid);
}
i.remove();
}
}
}
【问题讨论】:
-
您更新了
processData是错误的,因为它每次hasNext()检查都会调用Iterator#next()两次。 -
是的,明白了。所以我可以
clear同步块内的地图,同时按照您的建议使用 for 循环进行迭代,而不是在迭代器上使用 remove。 -
要么
clear在我的帖子中的同步块中,要么在您更新的示例中提取条目:while (i.hasNext()) { Entry<String, Long> e = i.next(); String clientid = e.getKey(); long timestamp = e.getValue(); // etc.。或者使用BlockingQueue,正如我在编辑后的帖子中所建议的那样。
标签: java multithreading thread-safety guava multimap