多线程服务器/客户端聊天应用程序中的ConcurrentModificationException

时间:2015-10-24 21:34:39

标签: java multithreading concurrency concurrentmodification

我制作了一个应用程序,这是一个非常简单的聊天功能ServerClient。聊天功能完全正常,但只要我关闭Client,我就会在ConcurrentModificationException中获得Server。应用程序仍然正常运行,但我知道我不应该收到该错误。我无法弄清楚为什么会这样。

这是我的服务器:

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;

public class Server extends JFrame {

    JTextArea textArea;
    // Serversocket.
    private static ServerSocket serverSocket = null;
    // Klientsocket.
    private static Socket clientSocket = null;
    // Datasamlingen av Threads.
    private ArrayList<ClientThread> threads = new ArrayList<>();

    public static void main(String args[]) {

        // Får (eller sätter default) port.
        int portNumber = 2000;
        if (args.length > 1) {
            portNumber = Integer.valueOf(args[0]);
        }

        new Server(portNumber);
    }

    public Server(int portNumber) {
        // Tar hand om JFrame.
        setLayout(new BorderLayout());
        setVisible(true);
        setLocation(750, 0);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        //Skapar och adderar JTextArea.
        textArea = new JTextArea(15, 60);
        JPanel centerPanel = new JPanel();
        centerPanel.add(new JScrollPane(textArea));
        add(centerPanel, BorderLayout.CENTER);
        pack();

        // Öppnar en serversoket med portnummret. Skriver också titel.
        try {
            serverSocket = new ServerSocket(portNumber);
            changeTitle();
        } catch (IOException e) {
            System.out.println(e);
        }

        // Vad som händer när man får ny klient.
        // Skapar en klientsocket till varje anknytning till servern och skapar en new klienttråd.
        while (true) {
            try {
                clientSocket = serverSocket.accept();
                ClientThread temp = new ClientThread(clientSocket, threads, this);
                temp.start();
                threads.add(temp);
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }

    public void writeInTextArea(String message) {
        // Metoden som används från ClientThread för att skriva någonting i textArean.
        textArea.append(message);
    }

    public void changeTitle() {
        // Metoden som används från ClientThread för att byta titel.
        String host = "";
        int portNumber = 0;
        try {
            host = serverSocket.getInetAddress().getLocalHost().getHostAddress();
            portNumber = serverSocket.getLocalPort();
        } catch (UnknownHostException e) {
            // Ignorera.
        }
        setTitle("CHAT | HOST: " + host + " | PORT: " + portNumber + " | NUMBER OF CLIENTS: " + threads.size());
    }
}


class ClientThread extends Thread {

    private BufferedReader in = null;
    private PrintWriter out = null;
    private Socket clientSocket = null;
    private ArrayList<ClientThread> threads;
    private Server frame;
    private String name;

    public ClientThread(Socket clientSocket, ArrayList<ClientThread> threads, Server frame) {
        this.clientSocket = clientSocket;
        this.threads = threads;
        this.frame = frame;
    }

    public void run() {
        try {
            // Öppnar nya input och output strömmar till den här klienten. Frågar efter klientens namn och
            // informerar alla att en ny klient har loggat in.
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            out = new PrintWriter(clientSocket.getOutputStream(), true);
            out.write("<<< Enter your name >>>\r");
            out.flush();
            name = in.readLine().trim();
            out.write("<<< Hi " + name + ". Welcome to the chat room >>>\n<<< Enter /quit in a new line to exit >>>\r");
            out.flush();
            //Byter titel.
            frame.changeTitle();
            synchronized (this) {
                for (ClientThread c : threads) {
                    if (c != this) {
                        c.out.write("<<< User " + name + " has connected >>>\r");
                        c.out.flush();
                    }
                }
                frame.writeInTextArea("<<< User " + name + " has connected >>>\n");
            }

            // Eviga loopen. Kollar om klienten skriver någonting och broadcast:ar den till alla.
            while (true) {
                String line = in.readLine();
                if (line.startsWith("/quit")) {
                    // Om klienten skriver /quit så kommer man ut ur eviga loopen.
                    break;
                }
                for (ClientThread c : threads) {
                    c.out.write("<" + name + "> " + line + "\r");
                    c.out.flush();
                }
                frame.writeInTextArea("<" + name + "> " + line + "\n");
            }

            // synchronized för att slippa krockar.
            synchronized (this) {
                // Informerar alla att klienten har loggat ut.
                for (ClientThread c : threads) {
                    if (c != null && c != this) {
                        c.out.write("<<< User " + name + " has disconnected >>>\r");
                        c.out.flush();
                    }
                }
                frame.writeInTextArea("<<< User " + name + " has disconnected >>>\n");
                out.write("<<< Goodbye " + name + " >>>");
                out.flush();

                // Tar bort tråden från ArrayList:an och stoppar den. Byter också titel.
                Iterator i = threads.iterator();
                while (i.hasNext()) {
                    ClientThread c = (ClientThread) i.next();
                    if (c == this) {
                        c.interrupt();
                        threads.remove(c);
                        frame.changeTitle();
                    }
                }

                // Stänger out, in och klientsocket:en.
                out.close();
                in.close();
                clientSocket.close();
            }
        } catch (IOException e) {
            // Vad som händer när en klient stängs ner (utan att skriva /quit, kanske om klienten stänger ner fönstret)
            // Synchronized för att slippa krockar.
            synchronized (this) {
                // Informerar alla att klienten har loggat ut.
                for (ClientThread c : threads) {
                    if (c != null) {
                        c.out.write("<<< User " + name + " has disconnected >>>\r");
                        c.out.flush();
                    }
                }
                frame.writeInTextArea("<<< User " + name + " has disconnected >>>\n");

                // Hittar tråden och stoppar och tar bort den från samlingen. Byter också titel.
                Iterator i = threads.iterator();
                while (i.hasNext()) {
                    ClientThread c = (ClientThread) i.next();
                    if (c == this) {
                        c.interrupt();
                        threads.remove(c);
                        frame.changeTitle();
                    }
                }

                // Stänger out, in och klientsocket.
                try {
                    out.close();
                    in.close();
                    clientSocket.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
}

这是我的客户:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.*;
import java.net.Socket;

public class Client extends JFrame {

    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;

    private JTextField inputTextField = new JTextField(60);
    private JTextArea textArea = new JTextArea(15, 60);

    // Tråd som kallas för att läsa rader från servern.
    private Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            String line;
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                // Eviga loopen
                while (true) {
                    line = in.readLine();
                    textArea.append(line + "\n");
                }
            } catch (IOException e) {
                // Ignorera.
            }
        }

    });

    // Vad som händer när man trycker på enter i JTextField
    private AbstractAction onEnterPressAction = new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            String textToSend = inputTextField.getText();
            inputTextField.setText("");
            try {
                // Skickar ut strängen till servern.
                out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"), true);
                out.write(textToSend + "\n");
                out.flush();
                // Om man skriver /quit. Stänger allt.
                if (textToSend.startsWith("/quit")) {
                    if (out != null) out.close();
                    if (in != null) in.close();
                    if (socket != null) socket.close();
                    System.exit(0);
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    };

    public Client(String host, int port) {
        // Konstruktorn.
        // Skapar en Socket.
        try {
            socket = new Socket(host, port);
        } catch (IOException e) {
            // Kunde inte kopplas till servern.
            JOptionPane.showMessageDialog(null, "Could not connect to server", "Connection Error", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
        }

        // Sätter titelrad.
        setTitle("CONNECTED TO SERVER: " + host + " IN PORT: " + port);

        // Startar eviga läsloopen.
        backgroundThread.start();

        // Tar hand om JFrame.
        setLayout(new BorderLayout());
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // Södra JPanel (JTextField som man skriver meddelande i)
        JPanel southPanel = new JPanel();
        southPanel.add(inputTextField);
        inputTextField.addActionListener(onEnterPressAction);

        // Centrala JPanel (JTextArea som visar alla meddelanden).
        JPanel centerPanel = new JPanel();
        JScrollPane scrollPane = new JScrollPane(textArea);
        centerPanel.add(scrollPane);

        // Adderar och packar.
        add(centerPanel, BorderLayout.CENTER);
        add(southPanel, BorderLayout.SOUTH);
        pack();
        setLocationRelativeTo(null);

    }

    public static void main(String[] args) {
        if (args.length == 0) {
            // Skickar default värden.
            new Client("127.0.0.1", 2000);
        } else if (args.length == 1) {
            // Skickar argument och default port värde.
            new Client(args[0], 2000);
        } else if (args.length == 2) {
            // Skickar argumenter.
            new Client(args[0], Integer.parseInt(args[1]));
        }
//        new Client("192.168.1.66", 2000);
    }
}

最后,这是我关闭客户端时出现的错误(如按X按钮或输入/退出):

Exception in thread "Thread-2" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.company.ClientThread.run(Server.java:186)

1 个答案:

答案 0 :(得分:0)

              Iterator i = threads.listIterator();
                while (i.hasNext()) {
                    ClientThread c = (ClientThread) i.next();
                    if (c == this) {
                        c.interrupt();
                        i.remove();
                        frame.changeTitle();
                    }
                }

使用Iterator删除元素。

如果在创建迭代器后修改列表,则会抛出异常。