【问题标题】:ConcurrentModificationException exception with iterator in multithread chat server多线程聊天服务器中带有迭代器的 ConcurrentModificationException 异常
【发布时间】:2012-05-29 05:55:29
【问题描述】:

我正在用 java 创建一个多线程聊天服务器。 当用户 u1 登录并向用户 u2 发送消息时,如果用户 u2 未连接,则将消息发送到服务器并放入待处理消息的 ArrayList 中。当用户 u2 连接时,他会收到来自服务器的消息,并将消息作为回执发送给用户 u1。

这是我的代码:

if (pendingmsgs.size()>0) {
    for(Iterator<String> itpendingmsgs = pendingmsgs.iterator(); itpendingmsgs.hasNext();) {
        //....parsing of the message to get the recipient, sender and text
        String pendingmsg = itpendingmsgs.next();

        if (protocol.author != null && protocol.author.equals(recipient)) {
            response+=msg;

            protocol.sendMsg(sender, "Msg "+text+" sent to "+recipient);

            itpendingmsgs.remove();
        }
    }   
}
out.write(response.getBytes(), 0, response.length());

这是 ServerProtocol sendMsg() 方法:

private boolean sendMsg(String recip, String msg) throws IOException {
    if (nicks.containsKey(recip)) { //if the recipient is logged in
        ClientConnection c = nick.get(recipient); //get the client connection 
        c.sendMsg(msg); //sends the message
        return true;
    } else {
        /* if the recipient is not logged in I save the message in the pending messages list */
        pendingmsgs.add("From: "+nick+" to: "+recip+" text: "+msg);
        return false;
    }
}

这是 ClientConnection sendMsg() 方法:

public void sendMsg(String msg) throws IOException {
        out.write(msg.getBytes(), 0, msg.length());
    }

其中 out 是一个 OutputStream。

当用户 u1 登录时,向未登录的用户 u2 发送消息,然后用户 u1 离开,当用户 u2 登录时,他没有收到消息,我收到此异常:

Exception in thread "Thread-2" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.remove(Unknown Source)
at ChatServer$ClientConnection.run(ChatServer.java:400)
at java.lang.Thread.run(Unknown Source)

第 400 行是

itpendingmsgs.remove();

我尝试过使用 CopyOnWriteArrayList,但它仍然不起作用。

【问题讨论】:

  • 对不起,第 400 行是它pendingmsgs.remove();

标签: java


【解决方案1】:

CopyOnWriteArrayList.iterator()doesn't support remove()。您可能应该使用Collections.synchronizedList(ArrayList)(在迭代期间按照 Javadoc 的规定正确锁定)。

这确实是允许一个线程添加到列表而另一个线程迭代删除元素的最简单方法。

【讨论】:

  • 我尝试使用private static ArrayList&lt;String&gt; alpendingmsgs = new ArrayList&lt;String&gt;(); private static List&lt;String&gt; pendingmsgs = Collections.synchronizedList(alpendingmsgs); 并输入private synchronized boolean sendMsg(String recip, String msg),但仍然出现错误。
  • 迭代时需要同步List;您不能只将方法标记为synchronized。阅读 Collections.synchronizedList 文档以获取代码示例。
  • 您的意思是在迭代列表之前放置synchronized(peningmsgs)?我仍然收到错误,现在在 next() 调用中。我不明白我应该如何同步它。
  • 如果您发布了不起作用的代码,我们会更好地提供帮助。
【解决方案2】:

很可能在查看您的代码之后,问题似乎是当您循环遍历您的迭代器时,您在 sendMsg 方法中向 ArrayList 添加了新内容

protocol.sendMsg(sender, "Msg "+text+" sent to "+recipient); // this line invokes the code which adds

pendingmsgs.add("From: "+nick+" to: "+recip+" text: "+msg); // this line adds a new item

请参阅this 讨论,了解上次发生这种情况的原因。

编辑:根据评论

第 400 行是 itpendingmsgs.remove();

这肯定是因为列表中的添加,当您到达itpendingmsgs.remove(); 时,您已经在列表中添加了一个新条目,这让您的迭代器抱怨。

更新:

解决此问题的方法:

  1. 使用 ListIteratoradd 代替 Iterator,从 ListIterator 中删除对象,而不是从基础 List 中删除。

更新示例代码:

package com.mumz.test.listiterator;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;

/**
 * Test Class to show case List Iterator.
 */
public class TestListIterator {

    /** The pendingmsgs. */
    List<String>    pendingmsgs = new ArrayList<String>();

    /**
     * Add some content to the list and then start processing the same list.
     */
    private void init() {
        addContentToList();
        doProcessing();
    }

    /**
     * Add test content to list.
     */
    private void addContentToList() {
        for (int iDx = 0; iDx < 10; iDx++) {
            pendingmsgs.add("Message " + iDx);
        }
    }

    /**
     * Randomly decide if message should be added or removed, showcase iteration using list iterator.
     */
    private void doProcessing() {
        if (pendingmsgs.size() > 0) {
            for(ListIterator<String> listIterator = pendingmsgs.listIterator(); listIterator.hasNext();){
                String currentMessage = listIterator.next();
                Random random = new Random();
                int nextInt = random.nextInt(100);
                if((nextInt % 2) == 0){
                    sendMsg(currentMessage, listIterator);
                } else {
                    listIterator.remove();
                }
            }
        }
    }

    /**
     * Add new content to the list using listIterator of the underlying list.
     * 
     * @param msg
     *            the msg
     * @param listIterator
     *            the list iterator
     * @return true, if successful
     */
    private boolean sendMsg(String msg, ListIterator<String> listIterator) {
        Random random = new Random();
        int nextInt = random.nextInt(10);
        // Randomly add new message to list
        if ((nextInt % 2) == 0) {
            listIterator.add("New Messsage : " + msg);
            return false;
        }
        return true;
    }

    /**
     * The main method.
     * 
     * @param args
     *            the arguments
     */
    public static void main(String[] args) {
        try {
            TestListIterator testListIterator = new TestListIterator();
            testListIterator.init();
            System.out.println(testListIterator);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return String.format("TestListIterator [pendingmsgs=%s]", pendingmsgs);
    }
}
  1. 不要使用IteratorListIterator 而是使用普通的for 或while 循环,在这种情况下,您可以直接修改您的集合(在这种情况下为列表)而不会出现此异常。

    李>
  2. 使用Iterator 本身,但不要在循环时将新元素添加到列表中。

    将您的消息添加到另一个列表,例如 tempMessageHolder,以便 sendMsg 将消息添加到此列表。

    循环完成后,将tempMessageHolder 中的所有消息添加到主列表pendingmsgs

【讨论】:

  • 谢谢。我正在尝试按照您的建议修复它,但也许我知道我遗漏了一些东西,因为这是我的方法,并且 add 不起作用 private boolean sendMsg(String dest, String msg, Iterator itpendingmsgs) throws IOException { if ( authors.containsKey(dest)) { ClientConnection c = authors.get(dest); c.sendMsg(味精);返回真; } else { itpendingmsgs.add("Da: "+author+" a: "+dest+" testo: "+msg);返回假; } }
  • 从代码 sn-p 我只能看到这一行直接添加到列表中 pendingmsgs.add("From: "+nick+" to: "+recip+" text: "+msg);跨度>
  • 好的,为什么我会得到“方法 add(String) 对于 Iterator 类型是不可否认的”?
  • 很抱歉,没有注意到您使用的是 Iterator 而不是 ListIterator,ListIterator 上存在 add 方法,编辑了我的答案
  • 我正在尝试第一种方法,但是当我这样做时 for(ListIterator itpendingmsgs = (ListIterator) pendingmsgs.iterator(); itpendingmsgs.hasNext();) java 说我不能做这个演员。 ://
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-22
  • 1970-01-01
  • 2015-02-17
  • 2013-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多