简体   繁体   中英

ConcurrentModificationException exception with iterator in multithread chat server

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.

Most probably after looking at your code, issue seems to be that while you are looping through your iterator you add new content to the ArrayList in sendMsg method

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

See this discussion for reason why this happened last time around.

Edit: As per comment

line 400 is itpendingmsgs.remove();

This is definitely because of addition in the list, as when you reach itpendingmsgs.remove(); , you have already added a new entry in the list which makes your iterator complain.

Update:

Appraches to fix this issue:

  1. Instead of Iterator use ListIterator and add , remove objects from the ListIterator and not underlying List.

Update Sample Code :

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. Instead of using Iterator or ListIterator just use normal for or while loop, in this case you can directly modify your collection (list in this case) without getting this exception.

  2. Use Iterator itself but dont add new elements into the list while you are looping.

    Add your messages to another list say tempMessageHolder so sendMsg will add message to this list.

    Once your loop is complete, add all the messages from tempMessageHolder to your main list pendingmsgs

CopyOnWriteArrayList.iterator() doesn't support remove() . You should probably use a Collections.synchronizedList(ArrayList) (properly locked during iteration as specified in the Javadoc).

That's really the simplest way to allow one thread to add to the list and the other to iterate through removing elements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM