使用线程下载多个Java文件

15

我正在尝试使用线程下载与某个模式匹配的多个文件。该模式可能匹配1个、5个或10个不同大小的文件。

假设实际下载文件的代码在downloadFile()方法中,fileNames是匹配该模式的文件名列表。如何使用线程完成这个任务?每个线程将只下载一个文件。是否建议在for循环内创建新线程?

for (String name : fileNames){
    downloadFile(name, toPath);
}
5个回答

39

你真的希望使用ExecutorService而不是单独的线程,它更加干净、可能性能更高,并且可以让你更容易地在以后更改一些东西(如线程计数、线程名称等):

ExecutorService pool = Executors.newFixedThreadPool(10);
for (String name : fileNames) {
    pool.submit(new DownloadTask(name, toPath));
}
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
// all tasks have now finished (unless an exception is thrown above)

在类中的其他位置定义实际的工作任务DownloadTask

private static class DownloadTask implements Runnable {

    private String name;
    private final String toPath;

    public DownloadTask(String name, String toPath) {
        this.name = name;
        this.toPath = toPath;
    }

    @Override
    public void run() {
        // surround with try-catch if downloadFile() throws something
        downloadFile(name, toPath);
    }
}

shutdown()方法的名称非常令人困惑,因为它“将允许之前提交的任务在终止之前执行”。awaitTermination()声明了一个需要处理的InterruptedException异常。


第一个代码片段应该存在于一个方法中,并在每次调用时创建一个新的池。这是一种设计假设。让我们不要猜测,尝试在Tomcat或Jboss上部署这样的代码,并查看在调用关闭后,下载服务实例是否仍然可用以继续下一个下载调用。我曾经因为做同样的事情而吃过亏,但我不会拿这个作为打击你的借口。 - Bitmap
@Bitmap 看起来你有点不高兴,因为 Philipp 批评了你的解决方案(而且理由充分!)。上面的代码完全没问题,演示了通常的方法。没有必要挑剔。 - Voo
请问,如果我一开始无法知道fileNames有多少个,该怎么办呢?我的意思是用户可能会选择在页面1中下载单个文件,然后在页面3或5中再次下载。我想控制异步下载任务的数量上限为n。 - Alston
需要将类设置为静态吗?根据 https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html ,示例中的类不需要。 - lalameat
@lalameat 不必将内部类设为静态的,但是除非你需要引用外围类,否则我会保持它是静态的。这样可以更明显地了解内部类可以访问哪些数据,因此更容易进行推理。 - Philipp Reichart
显示剩余3条评论

5

是的,你可以在for循环中创建一个新的线程。像这样:

List<Thread> threads = new ArrayList<Thread>();
for (String name : fileNames) {
  Thread t = new Thread() {
    @Override public void run() { downloadFile(name, toPath); }
  };
  t.start();
  threads.add(t);
}
for (Thread t : threads) {
  t.join();
}
// Now all files are downloaded.

你还应该考虑使用Executor,例如,在由Executors.newFixedThreadPool(int)创建的线程池中。


使用上面的代码,我该如何限制同时下载的文件数量?比如说,我只想同时下载3个文件。您有使用Executor的示例吗? - user373201
@user373201:请跟随我提供的链接访问Executors.newFixedThreadPool(int)并查看@Phillip Reichart的答案。 - maerics

1

是的,您可以在代码中创建线程。

for (final String name : fileNames){
    new Thread() {
       public void run() {
           downloadFile(name, toPath);
       }
    }.start();
}

1

使用Executor,试一下吧。

ExecutorService  exec= Executors.newCachedThreadPool()
for (String name : fileNames){
 exec.submit(new Runnable()
 {
  public void run()
  {
    downloadFile(name, toPath);
  }
 });
}

如果您想同时运行三个下载任务,您可以使用以下代码:
Executors.newFixedThreadPool(3)

现在它在execute() Runnable时会阻塞,你应该调用exec.submit() :) - Philipp Reichart
@Philipp Reichart,你真的知道自己在说什么吗?哪些块是会阻塞执行的?听着兄弟!如果你不知道自己在说什么,就避免给别人打分。 - Bitmap
是的,我确实被这个问题咬过一次。Execute.execute() 可能会“在新线程中、在池化线程中或在调用线程中执行,由 Executor 实现自行决定。”你真的想将变量声明为 ExecutorService 并使用 submit() - Philipp Reichart
你因为过去调用“execute”时遇到问题,就开始打击别人了?!兄弟,你只是在开玩笑。 - Bitmap
我只会在确定答案不正确时才会投反对票,如果你修正了它,我很乐意取消反对票。根据使用的Executor实现,execute()可能会在调用线程上运行命令,而显然存在一种不会出现这个问题的方法,这是一个相当有说服力的理由。 - Philipp Reichart
那么接下来我应该添加什么到答案中呢?因为你已经表明只有在我选择按照你的方式进行时才会给我加分。 - Bitmap

0

所有上述方法都会创建线程,但实际上并没有实现并发。

ExecutorService pool = Executors.newFixedThreadPool(5);
final File folder = new File("YOUR_FILES_PATH");
int l = folder.listFiles().length;
System.out.println("Total Files----"+folder.listFiles().length);
long timeStartFuture = Calendar.getInstance().getTimeInMillis();
    pool.execute(new DownloadFile(folder,0,l/2));
    pool.execute(new DownloadFile(folder,(l/2),l));
    pool.shutdown();
    try {
        pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    long timeEndFuture = Calendar.getInstance().getTimeInMillis();
    long timeNeededFuture = timeEndFuture - timeStartFuture;
    System.out.println("Parallel calculated in " + timeNeededFuture + " ms");

以上程序用于实现并发,请根据您的需求进行修改。

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