我已经制作了一个 Swings 应用程序,但有一个问题如下:

我从事件调度线程启动了一个名为“Thread-Main”的 SwingWorker 线程,并将 GUI 的 JLabel 引用传递给“Thread-Main”。

现在我已经从“Thread-Main”启动了 10 个线程。

现在我希望所有 10 个线程都应该更新 JLabel。

我怎样才能做到这一点?

有人告诉我,我可以通过首先将 SwingWorker 的所有 10 个线程子类化,然后调用publish("") 方法并在该“publish”方法中传递字符串,然后通过以下方法收集所有已发布的字符串来做到这一点: “主线程”

@Override
protected void process(List<String> labelStrings) {
    String count = labelStrings.get(labelStrings.size() - 1);
    label.setText(count); // label is a reference to a label in the GUI 
}
  1. 上述做法是否正确?
  2. 这 10 个线程应该是 SwingWorker 的子类吗?
  3. 还有其他方法可以做到这一点吗?
有帮助吗?

解决方案

  1. - 这是错误的做法 如果你想控制线程数 你自己。为什么?因为如果你看 SwingWorker 代码你会看到它使用了 ThreadPoolExecutor 内部最多包含 10 个线程。如果您启动了多个 SwingWorker同时它们都将使用这个执行器运行。然而, 您无法直接控制后台线程是否并行执行.
  2. 参见第 1 点。
  3. 我推荐的解决方案是:

    • 创建一个单 SwingWorker.
    • doInBackground() 方法直接启动 10 个线程或使用 ExecutorService.
    • 用一个 CountDownLatch 或者 CompletionService 在主线程之间同步(即 SwingWorker 后台线程)和工作线程。

例子

定义要调用的工作线程数并声明要更新的 JLabel。

final int nThreads = 10;
JLabel myLbl = new JLabel();

将我们希望执行的工作单元定义为 Callable<String>. 。这 String 结果将用于更新 JLabel.

private static class MyCallable implements Callable<String> {
  public String call() { ... }
}

现在开始一个 SwingWorker, ,这将依次启动多个并行工作线程来执行任何处理。工作人员不会通过调用返回结果 done() (因此 Void 类型)但是 将要 马歇尔中间 String 通过调用将结果返回到 Swing 线程 process(String... chunks).

new SwingWorker<Void, String>() {
  // See method definitions below.
}.execute();

定义 doInBackground() 使用以下命令启动工作线程并阻止每个结果 CompletionService.

public Void doInBackground() throws Exception {
  // Define executor service containing exactly nThreads threads.
  ExecutorService execService = Executors.newFixedThreadPool(nThreads);


  // Define completion service that will contain the processing results.
  CompletionService compService = new ExecutorCompletionService(execService);    

  // Submit work to thread pool using the CompletionService.  Future<String>
  // instances will be added to the completion service's internal queue until complete.
  for (int i=0; i<nThreads; ++i) {
    compService.submit(new MyCallable());
  }

  // Take results from each worker as they appear and publish back to Swing thread.
  String result;
  while ((result = compService.take().get()) != null) {
    publish(result);
  }
}

现在我们实施 process(String... chunks) 简单地更新 JLabel 当被叫时。

public void process(String... chunks) {
  if (chunks.length > 0) {
    // Update label with last value in chunks in case multiple results arrive together.
    myLbl.setText(chunks[chunks.length - 1]);
  }
}

最后我们重写 done() 将任何异常编组回 Swing 线程。

public void done() {
  try {
    get(); // Will return null (as per Void type) but will also propagate exceptions.
  } catch(Exception ex) {
    JOptionPane.show ... // Show error in dialog.
  }
}

其他提示

也许更简单的方法是在SwingUtilities.invokeLater(...)方法中包装更新GUI的代码。

编辑:在您想要更新标签的个别线程中:

SwingUtilities.invokeLater(new Runnable()
{
    public void run()
    {
        label.setText(...);
    }
});

为什么你需要10个线程?为什么不会有一个额外的线程呢?

真正的问题:你要解决的问题是什么?

回答你的直接问题:

1)是的,这是正确的方法 2)是的,线程应该是SwingWorkers(如果你使用netbeans,你也可以使用Tasks,它们也是SwingWorker的子类)

3)如果你想从edt中获得一个单独的线程;然后你需要使用swingworker;所以方式就是这样做的。

祝你好运!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top