带有while循环和Thread.sleep()的Java同步块

时间:2014-07-24 17:11:51

标签: java multithreading locking

我想在课堂上同步两个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标记为按预期完成。

我可能以更好的方式实现我的程序来解决这个问题,但我想知道这种行为的逻辑解释是什么。

1 个答案:

答案 0 :(得分:0)

好的,不管发生了什么,我在sendTaskToAllEndpoints()内调用相同的task.taskIsDone(responses)方法,有效地将锁定在while循环中,直到新调用的任务也结束为止。

我通过创建一个AtomicInteger对象在每次进入synchronized块时递增来捕获这个bug,并在我离开它时将其递减(并将它们包含在try-finally块中)。第一个任务结束后,我发现AtomicInteger对象没有下降到0,它们在1到2之间上下移动。

似乎第一个任务成功结束,因为没有任何阻止回调执行,但是一旦第一个任务完成,并且task.taskIsDone()被调用,该方法本身就创建了另一个Task的实例,调用sendTaskToAllEndpoints(),因此阻止第二个任务的所有回调调用,依此类推,直到它们以超时结束。

谢谢大家的建议。