Java 如何从不同的线程将条目填充到映射中,然后从单个后台线程迭代映射并发送?
我有一个below类,其中有一个Java 如何从不同的线程将条目填充到映射中,然后从单个后台线程迭代映射并发送?,java,multithreading,thread-safety,guava,multimap,Java,Multithreading,Thread Safety,Guava,Multimap,我有一个below类,其中有一个add方法,另一个线程调用该方法来填充我的clientdtotimestampHoldermultimap。然后在下面的同一个类中,我启动一个后台线程,它每60秒运行一次,并调用一个processData()方法,该方法迭代相同的映射并将所有这些数据发送到其他服务 public class Handler { private final ScheduledExecutorService executorService = Executors.newSingle
add
方法,另一个线程调用该方法来填充我的clientdtotimestampHolder
multimap。然后在下面的同一个类中,我启动一个后台线程,它每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));
}
}
}
}
}
使用包装器对多重映射进行线程安全引用(ArrayListMultimap
是一个ListMultimap
,即在列表中存储值):
不遵循此建议可能会导致不确定性行为
在您的情况下,您必须手动同步entries视图的迭代,因为其迭代器未同步:
public void processData() {
synchronized (clientidToTimestampHolder) {
for (Map.Entry<String, Long> entry : clientidToTimestampHolder.entries()) {
String clientid = entry.getKey();
long timestamp = entry.getValue();
boolean isUpdated = isUpdatedClient(clientid, timestamp);
if (!isUpdated) {
updateClient(String.valueOf(clientid));
}
}
clientidToTimestampHolder.clear();
}
}
您可以(应该?)为数据创建自己的值类来存储
String
和long
字段,并使用它而不是通用的映射。现在输入,您的代码主要是观察映射不一致,因为在一次迭代中,您可能会有[1:“value1”,2:“value2”,3:“value3”]
在地图中,下一次迭代地图可能是[1:“value1”,2:“value2”,3:“value3”,4:“value4”]
。主要的问题是,我认为MultiMap不能确保元素排队的顺序(请参阅),因此您可以在迭代过程中跳过一个元素(由您决定它是否危险)
如果您确实需要停止每个put操作,那么确实可以使用@Xaerxess方法在processData()内同步映射。您提到的另一种可能性是制作一些,基本上是在多重贴图的快照上进行迭代,首先您要做:
public Multimap<String, Long> getClientidToTimestampHolder(){
return ImmutableSetMultimap.copyOf(clientidToTimestampHolder);
}
为什么需要同步?因为如果您想要在时间t获得精确的映射,那么您需要防止其他线程向其添加元素。这是通过Java实现的,因此,只要一个线程(这里是您的后台线程)在映射上获得了锁,其他线程就无法在您读取多映射时访问该多映射。还有,在完成处理后如何从映射中删除条目?那么我是否需要创建clientdtotimestampHolder
map的副本,然后将其传递给processData
map?基本上,我想每60秒处理clientidToTimestampHolder
map中的任何内容,并且processData
方法每60秒仅由单个后台线程调用,那么为什么我们需要同步该方法?@user1234,因为另一个线程可以在迭代引导期间修改multimap(可能)到ConcurrentModificationException
,您希望这里有确定性行为(文档中提到,不遵循此建议可能会导致非确定性行为)。好的,现在就知道了。。如果删除已处理的条目,则每隔60秒,它将再次开始处理旧条目,这又如何呢?正确的?基本上,我希望每隔60秒处理一次映射中的所有内容。迭代后,您可以在同步块内执行clientdtotimestampHolder.clear()
。但如果这是您的用例,那么您根本不需要多重映射,请使用BlockingQueue
。我将更新我的答案以显示我的意思。我不能跳过任何元素,因此我需要确保我正在迭代所有元素,一旦我完成处理,它应该从映射中删除,而不是永远保留在那里。那么你认为什么是最好的方法呢?删除我要添加代码的地方?它会出现在processData方法中吗?在这种情况下,我将使用@Xaerxess的答案并对迭代的集合进行同步,然后在同一个同步块中,放入remove方法(因此在processData中是)。因为只要不需要快照就可以创建防御副本,如果在迭代过程中需要停止添加元素,那么这是不够的,需要锁定对映射的所有访问。在我的问题中添加了更新。那就是你的意思?那正是我的意思!您更新了processData
是错误的,因为它在每次hasNext()
检查时调用了迭代器#next()
两次。是的,收到了。因此,我可以清除同步块中的映射,同时按照您的建议使用for循环进行迭代,而不是在迭代器上使用remove;long timestamp=e.getValue();//等
。或者在编辑后按照我在帖子中的建议使用BlockingQueue
。
// ...
public void processData() {
synchronized (clientidToTimestampHolder) {
for (Map.Entry<String, Long> entry : clientidToTimestampHolder.entries()) {
String clientid = entry.getKey();
long timestamp = entry.getValue();
boolean isUpdated = isUpdatedClient(clientid, timestamp);
if (!isUpdated) {
updateClient(String.valueOf(clientid));
}
}
clientidToTimestampHolder.clear();
}
}
private final LinkedBlockingQueue<Map.Entry<String, Long>> clientidToTimestampHolder =
new LinkedBlockingQueue<>();
public void add(final String clientid, final Long timestamp) {
clientidToTimestampHolder.offer(Maps.immutableEntry(clientid, timestamp));
}
public void processData() {
final List<Map.Entry<String, Long>> entries = new ArrayList<>();
clientidToTimestampHolder.drainTo(entries);
for (Map.Entry<String, Long> entry : entries) {
String clientid = entry.getKey();
long timestamp = entry.getValue();
boolean isUpdated = isUpdatedClient(clientid, timestamp);
if (!isUpdated) {
updateClient(String.valueOf(clientid));
}
}
}
public Multimap<String, Long> getClientidToTimestampHolder(){
return ImmutableSetMultimap.copyOf(clientidToTimestampHolder);
}
public void processData() {
Multimap<String, Long> tmpClientToTimestampHolder = getClientidToTimestampHolder();
for (Entry<String, Collection<Long>> entry : tmpClientToTimestampHolder.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));
}
}
}
}
synchronized (clientidToTimestampHolder){
clientidToTimestampHolder.remove(key, value);//fill key,value, or use removAll(key)
}