0

I'm creating a multithread chat server in java. When user u1 logs in and sends a message to user u2, if user u2 is not connected the message is sent to the server and put in an ArrayList of pending messages. When user u2 connects, he receive the message from the server and send a message to user u1 as a receipt.

This is my code:

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());

This is the ServerProtocol sendMsg() method:

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;
    }
}

and this is the ClientConnection sendMsg() method:

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

where out is an OutputStream.

When user u1 logs in, sends a message to user u2 who is not logged in and then user u1 leaves, when user u2 logs in he doesn't receive the message and I get this exception:

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)

Line 400 is

itpendingmsgs.remove();

I've tried using a CopyOnWriteArrayList but it still doesn't work.

4

2 回答 2

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

有关上次发生这种情况的原因,请参阅此讨论。

编辑:根据评论

第 400 行是 itpendingmsgs.remove();

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

更新:

解决此问题的方法:

  1. 而不是Iterator使用ListIteratorand add,从 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. 在这种情况下,您可以直接修改您的集合(在这种情况下为列表),而不是使用Iterator或仅使用普通的 for 或 while 循环,而不会出现此异常。ListIterator

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

    将您的消息添加到另一个列表说tempMessageHolder这样sendMsg会将消息添加到此列表。

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

于 2012-05-22T17:45:30.463 回答
1

CopyOnWriteArrayList.iterator() 不支持remove()。您可能应该使用 a Collections.synchronizedList(ArrayList)(在迭代期间正确锁定,如 Javadoc 中所指定)。

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

于 2012-05-22T17:45:31.280 回答