Java等待线程完成

94

我有一个线程正在下载数据,我希望在加载数据之前等待下载完成。 有没有标准的方法可以做到这一点?

更多信息:

我有一个Download类,从URL获取数据(序列化POJO)。 Download是Runnable和Observable。 它跟踪已下载的字节数和下载大小。 我有一个进度条显示用户的进度。 GUI观察Download以更新进度条。

当下载POJO时,我想获取它并移至下一步。 每个步骤都必须等待上一个步骤完成。 问题是我想不到一种方法来暂停我的应用程序以等待下载线程。 下载完成后,我想调用download.getObject(),它将返回数据作为对象。 然后我可以进行强制转换并继续下一次下载。

我有一个辅助类管理下载的URL并对所有Download调用进行处理。 此调用将调用getObject并进行强制转换。 GUI调用helper.getUser()。 辅助程序启动线程运行,并希望它“知道”何时完成,以便返回转换后的对象。

有什么建议/示例吗? 我处于设计的初级阶段,因此愿意进行更改。

更新:

我按照http://download.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html#get的方法并使用模式锁定,直到线程完成。 代码非常混乱,我不喜欢这种方法。 我将继续尝试找到处理下载进程工作流程的“干净”方法。

9个回答

110

Thread有一个方法可以为你完成这个任务,join,它会阻塞直到线程执行完毕。


1
我尝试使用join(),但这会阻止GUI更新。我使用Thread的原因是在下载过程中保持GUI更新。调用join会停止GUI更新。 - Allan
7
join()方法可行。如果GUI停止更新,则可能需要更详细地了解哪些线程需要加入(join)。我不会加入(Event Dispatch Thread)EDT。 - akf
4
您需要确保在Swing线程中不要调用join()方法。您可以通过创建一个负责下载线程的线程来实现这一点。这实际上只是一个后台工作线程,您可以忘记它的存在。工作线程知道何时下载完成以及在该点要执行的操作。您只需要确保对Swing对象所做的编辑都在Swing线程中完成即可。 - unholysampler
2
"join()"不会重新抛出在线程内部抛出的异常。因此,在"join()"释放当前线程之后,您无法确定线程是否成功完成。 - Ilya Serbis

78

你可以使用java.util.concurrent包中的CountDownLatch。在等待线程继续执行之前,当需要等待一个或多个线程完成时,它非常有用。

例如,等待三个任务完成:

CountDownLatch latch = new CountDownLatch(3);
...
latch.await(); // Wait for countdown

其他线程在完成它们的任务后,每个线程都会调用latch.countDown()。一旦倒计时完成(例如本例中是三),执行将继续。


3
适用于没有图形界面的应用程序,效果完美。 - JRichardsz
我想补充一句,这是一个非常灵活的解决方案,并不一定适用于等待所有线程。如果你只想等待“第一个”线程完成,可以将门闩设置为 new CountDownLatch(1)。这是处理多个并发线程最好的答案。 - Arix Zajicek
完美,正是我在寻找的。对于非图形用户界面应用程序来说,这是最简单的解决方案。 - undefined

18

随着时间的推移,出现了更好的join()方法替代方案。

ExecutorService.html#invokeAll是其中之一。

执行给定的任务,当所有任务完成时,返回一个持有它们状态和结果的Future列表。对于返回列表中的每个元素,Future.isDone()都为true。

请注意,已完成的任务可能是正常终止或通过引发异常终止。如果在此操作正在进行时修改了给定的集合,则此方法的结果未定义。

ForkJoinPoolExecutors.html#newWorkStealingPool 提供了实现相同目的的其他替代方案。

示例代码片段:

import java.util.concurrent.*;

import java.util.*;

public class InvokeAllDemo{
    public InvokeAllDemo(){
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        List<MyCallable> futureList = new ArrayList<MyCallable>();
        for ( int i=0; i<10; i++){
            MyCallable myCallable = new MyCallable((long)i);
            futureList.add(myCallable);
        }
        System.out.println("Start");
        try{
            List<Future<Long>> futures = service.invokeAll(futureList);  
        }catch(Exception err){
            err.printStackTrace();
        }
        System.out.println("Completed");
        service.shutdown();
    }
    public static void main(String args[]){
        InvokeAllDemo demo = new InvokeAllDemo();
    }
    class MyCallable implements Callable<Long>{
        Long id = 0L;
        public MyCallable(Long val){
            this.id = val;
        }
        public Long call(){
            // Add your business logic
            return id;
        }
    }
}

1
我理解的对吗:所有的Callable都会并行执行,而不是依次执行? - Johnny Five

17
您可以使用join()等待所有线程完成。就像下面这样:
for (int i = 0; i < 10; i++) 
{
    Thread T1 = new Thread(new ThreadTest(i));                
    T1.start();   
    try {             
       T1.join(); 
    } catch (InterruptedException e) {
       e.printStackTrace();
    }
}

14

SwingWorker类有一个doInBackground()方法,您可以用它来执行任务。您可以选择调用get()并等待下载完成,或者覆盖done()方法,一旦SwingWorker完成,该方法将在事件分派线程上被调用。

与您当前的做法相比,使用Swingworker有许多优点,因为它具有您正在寻找的许多功能,所以没有必要重新发明轮子。您可以使用getProgress()setProgress()方法作为替代品,而不是在可运行对象上使用观察器来获取下载进度。正如我上面所述,done()方法在工作完成后调用,并在EDT上执行,这使得您可以在下载完成后加载数据。


5
我想你正在使用SwingWorker提供的后台线程调用下载操作。如果是这样,只需在同一个SwingWorker的doInBackground方法中按顺序调用下一段代码即可。

3

通常,当您想等待线程完成时,应调用其上的join()方法。


1
有什么建议或示例吗?我尝试过使用 SwingWorker... 但是代码非常混乱,我不喜欢这种方法。
可以使用 process()setProgress() 来显示中间结果,而不是等待完成的 get() 方法。可以参考这个简单的 示例 或者这个相关的 示例

0

join() 方法允许一个线程等待另一个线程的完成。然而,与sleep一样,join依赖于操作系统的时序管理,所以你不应该假设join会完全按照指定的时间等待。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接