【问题标题】:ArrayList doesn't work in a ThreadArrayList 在线程中不起作用
【发布时间】:2018-05-02 07:10:53
【问题描述】:

我正在使用一个新线程来搜索服务器,而不是在其他类(如图形等)中产生任何滞后。我发现服务器 ArrayList 的 .add() 方法不起作用,甚至servers.size() 在新线程中不起作用。你知道发生了什么以及如何解决这个问题吗?

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

另外,这段代码还有另一个问题:Main.chatGraphics.msgInputTF.setText("Wait...") 不起作用。 setText 方法本身并不能仅在此方法中起作用。我认为这是因为 setEnabled(false) 方法紧随其后,但我不确定。你也可以帮我解决这个问题吗?

【问题讨论】:

  • 请定义“不起作用”。您确实知道 1) 您的 servers 变量应该是 volatile,并且 2) 您在线程有机会对其执行任何操作之前返回了 servers 变量,您根本不应该返回它,而是在回调机制中使用它,例如可通过 SwingWorker 或 Callable(不是 Runnable)获得。
  • 此外,这个Main.chatGraphics.msgInputTF.setEnabled(true); 不应在后台线程中调用,因为它违反了 Swing 线程规则。再次,使用 SwingWorker 并做正确的事情。请阅读Lesson: Concurrency in Swing
  • @HovercraftFullOfEels By doesn't work 我的意思是 servers.add() 不会向服务器 ArrayList 添加任何内容,而 servers.size() == 0 在使用 add() 后会给出 false t 工作,但是如果我将 servers.size() 打印到控制台,它会给出 0。
  • 如果这是我的代码,我会将这段代码与 GUI 分开并单独调试。分而治之。运行后,我会尝试通过 SwingWorker&lt;List&lt;String&gt;, Void&gt; 将其网格化到 GUI 中,并在工作人员的 doInBackground() 方法中返回列表。
  • @Hovercraft 你不能让它变得不稳定。一方面,您只能使字段易变,而不是局部变量。另一方面,这实际上是最终的,你不能使某些东西成为最终的和易变的。

标签: java multithreading swing arraylist


【解决方案1】:

来自Javadoc of ArrayList

请注意,此实现不同步。如果多个线程同时访问一个 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则它必须在外部同步。 (结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)这通常通过同步一些自然封装的对象来完成列表。如果不存在此类对象,则应使用 Collections.synchronizedList 方法“包装”该列表。这最好在创建时完成,以防止意外不同步访问列表:

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

您有多个线程同时访问此列表:调用方法的线程和新创建的线程。新创建的线程进行结构修改(它添加到列表中)。因此,您需要外部同步。

将您的ArrayList 包装在synchronizedList 中。

【讨论】:

  • 我已经尝试过使用synchronizedList,但也没有用。我是这样写的: List servers = Collections.synchronizedList(new ArrayList());
  • 我认为这不会解决他的问题。 ArrayList 应该是线程使用的 Callable 本地的,因此在 Callable 完全完成之前不会真正在单独的线程中使用。
【解决方案2】:

根据您发布的代码,您的List&lt;String&gt; 甚至不应该暴露给多个线程,而应该是工作线程本身的本地,并且仅在工作线程完成其工作后使用。我建议您使用SwingWorker&lt;List&lt;String&gt;, Void&gt;——一个工作线程,一旦完成工作,它将在回调中返回字符串列表。所有的后台工作都在 worker 的 doInBackground() 方法中完成,并且该方法中不应存在 Swing 代码。在 Swing 事件线程上调用 worker 的 done() 方法,因此 Swing 代码可以而且应该出现在此处。这些方面的东西可能会起作用,但如果它们不起作用,那么您可能会在未显示的代码中的其他地方遇到问题,需要进行进一步的调试(未编译或测试的代码):

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

再次了解更多信息,请阅读:Lesson: Concurrency in Swing

这一切的关键在于:

  • List 再次位于工作线程的本地
  • 您获得 List 并开始使用它,而不是作为方法的直接返回(请注意,我将 findServers 声明为 void),而是在回调中,这里工作人员的 done() 方法仅在工作人员已完成其操作。
  • 再次注意避免从后台线程进行 Swing 调用。

【讨论】:

  • 非常感谢,但是 setText() 再次不起作用,我仍然需要在 doInBackground() 方法中捕获错误,因为如果 done() 方法捕获任何错误,那么它只会停止进一步代码的执行。
  • @nexusgeniuz:那么你可以这样做——处理 doInBackground 中的错误,但如果 GUI 需要处理它,请传播任何上游。至于setText(...),如果代码连接正确,它将起作用。这是另一个需要解决的问题。我支持这个答案,并以在该领域多年的经验支持它。
  • 您能解释一下为什么不建议将 set(...) 放在 doInBackground() 中吗?当我这样做时,一切都很好。
  • @nexusgeniuz:如果你的意思是.setText(...),因为这样做意味着你正在从 Swing 事件线程中进行 mutational Swing 调用。这将导致难以调试 intermittent 异常被抛出。是的,您的代码有时会起作用,但有时不会,而且不可靠的代码比没有代码更糟糕。你真的需要阅读我已经给你好几次的教程链接。它解释了一切。
  • "I mean how does it violate Swing threading rules?":请理解Swing 不是线程安全的。再次参见Lesson: Concurrency in Swing。请特别阅读this page
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-24
  • 2020-11-01
  • 1970-01-01
  • 2014-05-16
相关资源
最近更新 更多