简体   繁体   中英

ArrayList doesn't work in a Thread

I'm using a new Thread to search for servers and not to make any lags in other classes such as Graphics, etc. And I've found out that .add() method of the servers ArrayList doesn't work, even servers.size() doesn't work in the new thread. Do you know what is happening and how to solve this problem?

ArrayList<String> findServers(int howMany){
    ArrayList<String> servers = new ArrayList<>();
    Main.chatGraphics.log("<font face='arial' color='yellow'>Searching for servers...</font>");
    Main.chatGraphics.msgInputTF.setText("Wait...");
    Main.chatGraphics.msgInputTF.setEnabled(false);
    new Thread(() -> {
        Socket newSocket;
        for (int i = 2; i < 254; i++){
            if (servers.size() >= howMany)
                break;
            try {
                newSocket = new Socket();
                InetSocketAddress isa = new InetSocketAddress("192.168.1." + i, Main.DEFAULT_PORT);
                if (isa.isUnresolved())
                    continue;
                newSocket.connect(isa, 10);
                servers.add(newSocket.getInetAddress().getHostAddress()); // DOESN'T WORK <<
            } catch (Exception e) {
                e.getStackTrace();
            }
        }
        if (servers.size() == 0) // DOESN'T WORK TOO <<
            Main.chatGraphics.log("<font face='arial' color='red'>No available servers</font>");

        Main.chatGraphics.msgInputTF.setEnabled(true);
        Main.chatGraphics.msgInputTF.setText("");
        Main.chatGraphics.msgInputTF.grabFocus();
    }).start();

    return servers;
}

Also, I have another problem with this code: Main.chatGraphics.msgInputTF.setText("Wait...") doesn't work. The setText method doesn't work itself only in this method. I think it happens because setEnabled(false) method comes right after it, but I'm not sure. Can you help me with this one too?

From the Javadoc of ArrayList :

Note that this implementation is not synchronized . If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:

 List list = Collections.synchronizedList(new ArrayList(...)); 

You have multiple threads which access this list concurrently: the thread calling the method and the newly created thread. The newly-created thread makes structural modifications (it adds to the list). Hence, you need external synchronization.

Wrap your ArrayList in a synchronizedList .

Based on the code you've posted, your List<String> shouldn't even be exposed to multiple threads but rather should be local to the worker thread itself, and only used once the worker thread has completed its work. I suggest that you use a SwingWorker<List<String>, Void> -- a worker thread that will return the String list in a call back once it is done doing its job. All the background work is done within the worker's doInBackground() method, and no Swing code should be present within this method. The worker's done() method is called on the Swing event thread, and so Swing code can and should be present here. Something along these lines could work, but if they don't then you've likely got problems elsewhere in code not shown and need to do further debugging (code not compiled nor tested):

void findServers(int howMany) {
    // code run on the Swing event thread
    Main.chatGraphics.log("<font face='arial' color='yellow'>Searching for servers...</font>");
    Main.chatGraphics.msgInputTF.setText("Wait...");
    Main.chatGraphics.msgInputTF.setEnabled(false);

    // code run in background thread, that returns our List of interest
    new SwingWorker<List<String>, Void>() {

        @Override
        public List<String> doInBackground() throws Exception {

            // the List should be declared local within the worker
            List<String> servers = new ArrayList<>();
            Socket newSocket;
            for (int i = 2; i < 254; i++) {
                if (servers.size() >= howMany) {
                    break;
                }

                // don't catch exceptions wihin the worker. Do this in the
                // done() method.
                newSocket = new Socket();
                InetSocketAddress isa = new InetSocketAddress("192.168.1." + i,
                        Main.DEFAULT_PORT);
                if (isa.isUnresolved())
                    continue;
                newSocket.connect(isa, 10);
                servers.add(newSocket.getInetAddress().getHostAddress());
            }
            return servers;
        }

        @Override
        public void done() {
            try {
                // call the worker's get() method to retrieve the List
                // and to capture any exceptions
                List<String> servers = get();
                if (servers.size() == 0) {
                    Main.chatGraphics.log("<font face='arial' color='red'>No available servers</font>");
                }

                Main.chatGraphics.msgInputTF.setEnabled(true);
                Main.chatGraphics.msgInputTF.setText("");
                Main.chatGraphics.msgInputTF.grabFocus();


                // *** use servers in the GUI **here**


            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();

                // you will need more robust exception handling here
                // including extracting from this the true
                // underlying exception that was called
            }
        }
    }.execute();
}

Again for more on this, please read: Lesson: Concurrency in Swing

The key to all of this is the following:

  • the List again is local to the worker thread
  • You get the List and start using it not as a direct return from a method (note that I declared findServers to be void ), but within a call back, here the worker's done() method that runs only after the worker has completed its actions.
  • Again take care to avoid making Swing calls from background threads.

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