您认为获得线程工作结果的最佳方法是什么?想象一个线程做了一些计算,你如何警告主程序计算完成?
你可以每隔X毫秒轮询一些名为“job finished”的公共变量,或者顺便说一下,但是你会收到比他们可用时更晚的结果...主代码将会浪费时间等待对他们来说另一方面,如果使用较低的X,CPU将浪费多次轮询。
那么,你怎么知道Thread或者一些Threads已经完成了他们的工作呢?
很抱歉,如果它看起来与其他question类似,那可能是 eben 答案的原因,我想。我所测量的是运行大量线程并知道所有线程何时完成,而不进行轮询。
我更多地考虑使用多批线程在多个CPU之间共享CPU负载,并知道批处理何时完成。我认为可以使用 Future 对象完成,但阻止 获取 方法看起来很像隐藏锁,而不是我喜欢的东西。
感谢大家的支持。虽然我也喜欢 erickson 的答案,我认为 saua 是最完整的,而我是将在我自己的代码中使用。
答案 0 :(得分:25)
不要使用线程等低级结构,除非你绝对需要强大的功能和灵活性。
您可以使用ExecutorService,例如ThreadPoolExecutor至submit() 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 Runnable
。 run
方法在计算结果时调用回调。使用这种方法,您可以放弃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/