在多线程聊天服务器中使用迭代器的ConcurrentModificationException异常

时间:2012-05-22 17:41:03

标签: java

我正在用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,但它仍无效。

2 个答案:

答案 0 :(得分:1)

最有可能在查看代码之后,问题似乎是在循环遍历迭代器时,在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. 而不是Iterator使用ListIteratoradd,而是从ListIterator中移除对象而不是基础List。
  2. 更新示例代码:

    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循环,在这种情况下,您可以直接修改集合(在本例中为list),而不会出现此异常。

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

      将您的邮件添加到另一个列表tempMessageHolder,以便sendMsg将此邮件添加到此列表中。

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

答案 1 :(得分:1)

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

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