从同步列表中删除元素的正确方法

时间:2012-02-27 16:44:44

标签: java multithreading concurrency client-server

我有一个客户端 - 服务器样式设计。我所做的是创建一个名为RequestController的类,它控制和监视作为线程对象发出的所有服务器请求。

这些请求线程在同步Map中跟踪(使用Collections.synchronizedMap(new HashMap<Long, RequestThread>())创建)。检查每个请求线程的每半秒,如果它仍处于活动状态,它会向其感兴趣的侦听器对象(请求线程的属性)发送进度消息(如果已完成或失败),它会通知感兴趣的侦听器并将其自身从synchronized {当Map方法完成时,{1}}。

我用来跟踪每个请求的run()对象是监视所有请求的RequestThread对象的受保护内部类。因此它可以访问包含它的地图。

我在这里遇到问题。当从地图中删除“死线程”时,我得到并发修改异常。我对地图的所有访问都是同步的,所以我不明白我是如何得到例外的。

这是控制循环代码:

RequestController

this.reqTimer = new Timer("Request Timer", true); TimerTask reqTask = new TimerTask() { @Override public void run() { synchronized(reqList) { for(RequestThread rt : reqList.values()) rt.updateProgress(); } } }; this.reqTimer.scheduleAtFixedRate(reqTask, 500L, 500L); 方法的最后,调用RequestThread.run()方法将其自身从地图中移除,如下所示:

RequestController

如果 public void removeRequest(long id) { synchronized(this.reqList) { this.reqList.remove(id); } } 方法在RequestThread.run()方法循环遍历调用TimerTask.run()方法的请求映射时完成updateProgress(),则不会阻止删除但允许更改映射并导致我的例外。两个不同的线程如何同时锁定同一个对象?删除是否应该被阻止,因为它在更新完成之前起源于另一个线程?

2 个答案:

答案 0 :(得分:2)

我要把我的意见转回答案。

  

两个不同的线程如何同时锁定同一个对象?

他们不能。并发修改必须在其他地方。我怀疑你的updateProgress()正在从列表中删除。这可能是发生并发修改异常的地方。

for (RequestThread rt : reqList.values()) {
    // you can't make any changes to reqList inside of the loop
    rt.updateProgress();
}

如果您需要updateProgress()从列表中删除请求,则可以:

  • 使用迭代器并将迭代器传递到updateProgress(),以便它可以调用iterator.remove()
  • List<RequestThread>传递给updateProgress()并添加要删除的请求。然后,当您在for循环之外(但仍在同步块中)时,您可以从reqList中删除删除列表中的项目。

答案 1 :(得分:2)

您需要在列表中之后执行删除: -

  synchronized (reqList) {
    // Keep track of items to be removed.
    List<RequestThread> remove = new LinkedList<RequestThread>();
    for (RequestThread rt : reqList.values()) {
      rt.updateProgress(remove);
    }
    // Remove them.
    reqList.removeAll(remove);
  }

或者您需要使用Iterator并使用其remove方法。