获取线程的输出

时间:2008-12-09 13:01:39

标签: java multithreading

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

你可以每隔X毫秒轮询一些名为“job finished”的公共变量,或者顺便说一下,但是你会收到比他们可用时更晚的结果...主代码将会浪费时间等待对他们来说另一方面,如果使用较低的X,CPU将浪费多次轮询。

那么,你怎么知道Thread或者一些Threads已经完成了他们的工作呢?

很抱歉,如果它看起来与其他question类似,那可能是 eben 答案的原因,我想。我所测量的是运行大量线程并知道所有线程何时完成,而不进行轮询。

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

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

7 个答案:

答案 0 :(得分:25)

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

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

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

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

我想澄清阻止get()

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

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

答案 1 :(得分:4)

你可以创建一个主程序实现的lister接口,一旦它完成了它的工作就会被worker调用。

这样你根本不需要轮询。

以下是一个示例界面:

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

答案 2 :(得分:1)

轮询a.k.a忙碌的等待不是一个好主意。正如您所提到的,忙等待会浪费CPU周期,并可能导致应用程序无响应。

我的Java很粗糙,但你需要以下内容:

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

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

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

答案 3 :(得分:1)

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

在这种情况下,孩子Thread / Runnable会获得一个监听器,该监听器知道如何处理结果,并在孩子Thread时回叫/ Runnable完成。

答案 4 :(得分:1)

你的情景仍然有点不清楚。

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

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

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

答案 5 :(得分:1)

子类Thread,并为您的类提供一个返回结果的方法。调用该方法时,如果尚未创建结果,则使用Thread连接()。当join()返回时,Thread的工作将完成,结果应该可用;把它归还。

仅当您确实需要触发异步活动,在等待时执行某些操作然后获取结果时才使用此选项。否则,线程的重点是什么?您也可以编写一个完成工作的类,并在主线程中返回结果。

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

答案 6 :(得分:0)

如saua所述:使用java.util.concurrent提供的构造。如果你坚持使用1.5之前的(或5.0)JRE,那么你可能会采用自己的方式进行滚动,但使用反向移动仍然可以做得更好:http://backport-jsr166.sourceforge.net/