【问题标题】:Getting the output of a Thread获取线程的输出
【发布时间】:2010-09-26 01:01:02
【问题描述】:

您认为获取线程工作结果的最佳方式是什么?想象一个线程进行一些计算,你如何警告主程序计算完成?

您可以每隔 X 毫秒轮询一次名为“作业已完成”的公共变量或其他内容,但随后您会比可用时更晚收到结果...主代码将浪费时间等待为他们。另一方面,如果你使用较低的 X,CPU 会被浪费很多次轮询。

那么,你要怎么做才能知道线程或某些线程已经完成了他们的工作?

抱歉,如果它看起来与其他 question 相似,我想这可能是 eben 答案的原因。我的意思是运行大量线程并知道它们何时完成,而不是轮询它们。

我更多地考虑使用批次线程在多个 CPU 之间共享 CPU 负载,并知道批次何时完成。我想这可以用 Future 的对象来完成,但是阻塞 get 方法看起来很像隐藏的锁,而不是我喜欢的东西。

感谢大家的支持。虽然我也喜欢 erickson 的回答,但我认为 saua 是最完整的,也是我'将在我自己的代码中使用。

【问题讨论】:

  • 这和question非常相似。
  • > 并知道批次何时完成。你为什么想知道这个?你需要那个批次的东西吗?或者您想知道何时可以安排更多任务?

标签: java multithreading


【解决方案1】:

不要使用线程等低级结构,除非您绝对需要强大的功能和灵活性。

您可以使用ExecutorService,例如ThreadPoolExecutorsubmit() Callables。这将返回一个Future 对象。

使用Future 对象,您可以轻松检查它是否已完成并获得结果(如果尚未完成,则包括阻塞get())。

这些结构将大大简化最常见的线程操作。

我想澄清一下屏蔽get()

这个想法是你想运行一些任务(Callables)来完成一些你不需要结果的工作(计算、资源访问等)现在。您可以只依靠Executor 随时运行您的代码(如果它是ThreadPoolExecutor,那么只要有空闲线程可用,它就会运行)。然后在某个时间点,您可能需要计算结果才能继续。此时您应该致电get()。如果此时任务已经运行,那么get() 将立即返回该值。如果任务未完成,则get() 调用将等到任务完成。这通常是需要的,因为无论如何您都无法在没有任务结果的情况下继续。

如果您不需要该值继续,但想知道它是否已经可用(可能会在 UI 中显示某些内容),那么您可以轻松调用 isDone() 并且仅在以下情况下调用 get()返回true)。

【讨论】:

    【解决方案2】:

    您可以创建一个主程序实现的列表器接口,一旦工作人员完成工作,它就会调用它。

    这样你根本不需要投票。

    这是一个示例界面:

    /**
     * Listener interface to implement to be called when work has
     * finished.
     */
    public interface WorkerListener {
        public void workDone(WorkerThread thread);
    }
    

    这是一个实际线程的示例,它执行一些工作并通知它的侦听器:

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    /**
     * Thread to perform work
     */
    public class WorkerThread implements Runnable {
        private List listeners = new ArrayList();
        private List results;
    
        public void run() {
            // Do some long running work here
    
            try {
                // Sleep to simulate long running task
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            results = new ArrayList();
            results.add("Result 1");
    
            // Work done, notify listeners
            notifyListeners();
        }
    
        private void notifyListeners() {
            for (Iterator iter = listeners.iterator(); iter.hasNext();) {
                WorkerListener listener = (WorkerListener) iter.next();
                listener.workDone(this);
            }
        }
    
        public void registerWorkerListener(WorkerListener listener) {
            listeners.add(listener);
        }
    
        public List getResults() {
            return results;
        }
    }
    

    最后,主程序启动一个工作线程并注册一个监听器,以便在工作完成后得到通知:

    import java.util.Iterator;
    import java.util.List;
    
    /**
     * Class to simulate a main program
     */
    public class MainProg {
        public MainProg() {
            WorkerThread worker = new WorkerThread();
            // Register anonymous listener class
            worker.registerWorkerListener(new WorkerListener() {
                public void workDone(WorkerThread thread) {
                    System.out.println("Work done");
                    List results = thread.getResults();
                    for (Iterator iter = results.iterator(); iter.hasNext();) {
                        String result = (String) iter.next();
                        System.out.println(result);
                    }
                }
            });
    
            // Start the worker thread
            Thread thread = new Thread(worker);
            thread.start();
    
            System.out.println("Main program started");
        }
    
        public static void main(String[] args) {
            MainProg prog = new MainProg();
        }
    }
    

    【讨论】:

      【解决方案3】:

      轮询(又名忙碌的等待)不是一个好主意。正如您所提到的,忙碌的等待会浪费 CPU 周期,并可能导致您的应用程序看起来没有响应。

      我的 Java 很粗糙,但您想要类似以下内容:

      如果一个线程必须等待另一个线程的输出,你应该使用条件变量。

      final Lock lock = new ReentrantLock();
      final Condition cv = lock.newCondition();
      

      对其他威胁的输出感兴趣的线程应该调用cv.wait()。这将导致当前线程阻塞。当工作线程完成工作时,它应该调用cv.signal()。这将导致被阻塞的线程被解除阻塞,允许它检查工作线程的输出。

      【讨论】:

        【解决方案4】:

        作为 Saua 描述的并发 API 的替代方案(如果主线程不需要知道工作线程何时完成),您可以使用发布/订阅模式。

        在这种情况下,子Thread/Runnable 被赋予一个监听器,它知道如何处理结果,并在子Thread/Runnable 完成时回调该监听器。

        【讨论】:

          【解决方案5】:

          你的场景还是有点不清楚。

          如果您正在运行批处理作业,您可能需要使用invokeAll。这将阻塞您的主线程,直到所有任务完成。这种方法没有“忙等待”,主线程会浪费 CPU 轮询 Future 的 isDone 方法。虽然此方法返回Futures 的列表,但它们已经“完成”。 (还有一个重载版本可以在完成之前超时,这对于某些任务可能更安全。)这比尝试自己收集一堆 Future 对象并尝试检查它们的状态或阻止要干净得多分别在他们的get 方法上。

          如果这是一个交互式应用程序,任务偶尔会在后台执行,那么按照nick.holt 的建议使用callback 是一个很好的方法。在这里,您使用submitRunnablerun 方法在计算结果时调用回调函数。使用这种方法,您可以丢弃submit 返回的Future,除非您希望能够在不关闭整个ExecutorService 的情况下运行cancel 任务。

          如果您希望能够取消任务或使用超时功能,要记住的重要一点是通过在其线程上调用interrupt 来取消任务。因此,您的任务需要定期检查其interrupted status 并根据需要中止。

          【讨论】:

            【解决方案6】:

            子类线程,并给你的类一个返回结果的方法。调用该方法时,如果尚未创建结果,则使用线程 join()。当 join() 返回时,您的线程的工作将完成并且结果应该可用;退货。

            仅当您确实需要触发异步活动,在等待时做一些工作,然后获得结果时才使用它。否则,线程的意义何在?您不妨编写一个类来完成工作并在主线程中返回结果。

            另一种方法是回调:让您的构造函数接受一个参数,该参数实现了一个带有回调方法的接口,该回调方法将在计算结果时被调用。这将使工作完全异步。但是,如果您在某个时候需要等待结果,我认为您仍然需要从主线程调用 join()。

            【讨论】:

              【解决方案7】:

              如 saua 所述:使用 java.util.concurrent 提供的构造。如果您坚持使用 1.5(或 5.0)之前的 JRE,您可能会采用自己的方式,但您最好还是使用后向端口:http://backport-jsr166.sourceforge.net/

              【讨论】:

                猜你喜欢
                • 2023-03-21
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-09-08
                • 2013-10-17
                • 2014-02-15
                相关资源
                最近更新 更多