我想在课堂上同步两个HashMap
。
我维护两个映射的原因是我将任务分派给不同的服务器,并且我将原始任务对象保存在一个HashMap<String, Task>
中,同时我在另一个{{1}中管理响应状态}}。
HashMap<String, HashMap<InetAddress, TaskResponse>
的密钥均为HashMaps
,对于每个String
对象而言都是唯一的。
所以我的代码是这样的:
Task
因此,为了概念性地解释我的代码所做的事情,HashMap<String, Map<InetAddress, TaskResponse>> taskResponseMap = new HashMap<>();
HashMap<String, Task> taskMap = new HashMap<>();
public void endpointHasRespondedCallback(TaskResponse taskResponse, InetAddress from) {
// Callback threads gets blocked here!
synchronized(taskResponseMap) {
synchronized (taskMap) {
Map<InetAddress, TaskResponse> taskResponses = taskResponseMap.get(taskResponse.taskUuid);
if (taskResponses == null || !taskResponses.containsKey(from)) {
// The response does not exists probably due to timeout
return;
}
taskResponses.put(from, taskResponse);
}
}
}
public void sendTaskToAllEndpoints(Task task) {
long taskStartedAt = System.currentTimeMillis();
HashMap<InetAddress, TaskResponse> taskResponses = new HashMap<>();
taskResponseMap.put(task.taskUuid, taskResponses);
taskMap.put(task.taskUuid, task);
for (InetAddress dest : getDestinationNodes()) {
sendTaskTo(dest, task);
messageResponses.put(dest, TaskResponse.emptyTaskResponse());
}
// Should wait for response to comeback till the timeout is over
while (System.currentTimeMillis() < taskStartedAt + timeoutInMillis) {
Thread.sleep(1000);
synchronized(taskResponseMap) {
synchronized (taskMap) {
if(isTaskOver(task.taskUuid)) {
Map<InetAddress, TaskResponse> responses = taskResponseMap.remove(task.taskUuid);
taskMap.remove(task.taskUuid);
task.taskIsDone(responses);
return;
}
}
}
}
// If the task is still sitting there, then it must have timed out!
synchronized(taskResponseMap) {
synchronized (taskMap) {
taskResponseMap.remove(task.taskUuid);
taskMap.remove(task.taskUuid);
}
}
}
// Do not synchronize purposefully since it is only being called in synchronized methods
public boolean isTaskOver(String taskUuid) {
Task task = taskMap.get(taskUuid);
if (task == null || !taskResponseMap.containsKey(task.taskUuid)) {
return true;
} else {
for (TaskResponse value : taskResponseMap.get(task.taskUuid).values()) {
if (value.status != TaskResponseStatus.SUCCESSFUL) {
return false;
}
}
}
return true;
}
方法将sendTaskToAllEndpoints()
个对象发送到远程端点,并等待Task
循环内的超时。
每当远程端点响应时,它都会执行while
方法,以便在endpointHasRespondedCallback()
中将其标记为已完成。
回到TaskResponseMap
方法,我检查任务是否使用sendTaskToAllEndPoints()
辅助方法完成,如果没有,我只是继续调用isTaskDone()
等待一秒钟直到我检查它下次。
我面临的问题是,即使我看到{I}}方法正在为我调度的所有远程节点执行(我使用日志语句验证),它也会在{{1}之外等待只有在Thread.sleep(1000)
方法中发生超时时才会进入块,因此即使所有节点都已正确响应,我的任务也会一直超时。
这是我没想到的,因为即使我在endpointHasRespondedCallback()
循环中获取两个对象上的锁定,我也会在睡觉之前将其解锁,我认为synchronized
线程进入休眠状态,等待锁定的其他线程应该使用sendTaskToAllEndpoints()
方法获取锁定,并将while
标记为按预期完成。
我可能以更好的方式实现我的程序来解决这个问题,但我想知道这种行为的逻辑解释是什么。
答案 0 :(得分:0)
好的,不管发生了什么,我在sendTaskToAllEndpoints()
内调用相同的task.taskIsDone(responses)
方法,有效地将锁定在while循环中,直到新调用的任务也结束为止。
我通过创建一个AtomicInteger
对象在每次进入synchronized块时递增来捕获这个bug,并在我离开它时将其递减(并将它们包含在try-finally块中)。第一个任务结束后,我发现AtomicInteger
对象没有下降到0,它们在1到2之间上下移动。
似乎第一个任务成功结束,因为没有任何阻止回调执行,但是一旦第一个任务完成,并且task.taskIsDone()
被调用,该方法本身就创建了另一个Task
的实例,调用sendTaskToAllEndpoints()
,因此阻止第二个任务的所有回调调用,依此类推,直到它们以超时结束。
谢谢大家的建议。