【问题标题】:Why aren't my threads start at the same time? Java为什么我的线程不能同时启动?爪哇
【发布时间】:2010-12-27 19:35:01
【问题描述】:

我有可变数量的线程用于并行下载。我用过这个,

for(int i = 0; i< sth; i++){
       thrList.add(new myThread (parameters));
       thrList.get(i).start();
       thrList.get(i).join();

}

我不知道为什么,但他们在等待对方完成。使用线程时,我应该得到混合打印输出,因为那时有几个线程运行该代码。但是,当我将它们打印出来时,它们总是有序的,一个线程等待前一个线程首先完成。我只希望他们加入主线程,而不是互相等待。我注意到当我在并行下载时测量时间。

我该如何解决这个问题?他们为什么要按顺序做?

在我的 .java 中, 有带有运行的 MyThread 类,有带有静态方法和变量的 Downloader 类。他们会是造成这种情况的原因吗?静态方法和变量?

我该如何解决这个问题?

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    您正在创建一个线程,等待它完成(加入),创建一个新线程,等待它完成(加入)等等。

    您应该阅读有关线程的 java 文档以了解大多数方法的作用: http://download.oracle.com/javase/tutorial/essential/concurrency/index.htmlhttp://download.oracle.com/javase/6/docs/api/java/lang/Thread.html

    【讨论】:

    • 哦,我明白了。我该如何解决?
    • @Ada:摆脱对join()的调用
    • 我加入了另一个 for 循环,但它没有改变。它仍然是串行下载,而不是并行下载。
    • 您的Downloadersynchronized 上是否有任何静态方法?这可能是问题的原因。否则我们需要更多地了解DownloaderMyThread 的实现。
    • 我们无法为您提供解决方案,说明为什么它仍然无法正常工作。多线程和同步是一个有很多微妙之处的困难主题,因此请阅读大量相关内容并尝试在学习的同时改进您的程序。在我看来,您正试图在一个工作单元中做很多事情(Downloader 和/或MyThread),因此请尝试将它们分成更小的单元。
    【解决方案2】:

    简单。问题出在这里:

    for(int i = 0; i< sth; i++){
           thrList.add(new myThread (parameters));
           thrList.get(i).start(); // problem
           thrList.get(i).join();  // problem
    }
    

    具体来说,循环的每次迭代都会创建一个线程,启动它然后等待它再次加入。这就是它们串行运行的原因。

    你需要做的是:

    for(int i = 0; i< sth; i++){
           thrList.add(new myThread (parameters));
           thrList.get(i).start();
    }
    
    for(int i = 0; i< sth; i++){
           thrList.get(i).join();
    }
    

    这将经历启动所有线程的过程,然后它将循环遍历每个线程并等待它退出。

    【讨论】:

    • 他们还在等对方不知道为什么
    • @ada 当然,这也取决于线程在做什么。如果线程正在访问阻塞资源(它们必须等待并一次访问一个),这可能是一个问题。我不知道没有看到更多。其他人指出文档和 Executor 服务看起来确实更整洁。我的方法简单又快速。
    • 另外,你好,downvoter,想评论一下这到底有什么问题?我没有发现任何问题,我的涉及 sleep 和 system.outs 的演示可以并行运行。
    【解决方案3】:

    使用 ExecutorService 会更好

    ExecutorService es = Executors.newCachedThreadPool();
    // pool is reusable
    
    List<Callable<Void>> callables = new ArrayList<Callable<Void>>();
    for(int i=0;i<sth;i++) callables.add(new MyCallable(i));
    for(Future<Void> futures : es.invokeAll(callables))
        futures.get();
    

    【讨论】:

    • 这个不会等对方吧?即使在分离 join() 循环之后,我的仍然可以。
    • 我不知道带有参数 i 的 MyRunnable 是什么。是我的 myThread 吗?
    • 它说“ExecutorService 类型中的方法 invokeAll(Collection extends Callable>) 不适用于参数 (List)”
    • 感谢 Ada,将 Runnable 更改为 Callable。如果您想等待结果/完成。也为此添加代码。
    【解决方案4】:

    您启动一个线程并立即加入其中,从而使整个线程无用,因为您正在等待线程完成。

       thrList.get(i).start();
       thrList.get(i).join();
    

    您应该使用同步队列,生成的线程自己从中获取参数,等待队列为空,然后继续。

    【讨论】:

      【解决方案5】:

      join 方法等待所选线程死亡。当前线程(执行触发事件的循环的线程)正在等待您刚刚开始死亡的线程,然后再开始执行下一个线程。

      【讨论】:

      • 对不起,我没有得到 System.getCurrentThread().join()。我应该在运行时调用它吗?它会让子线程完成并加入主线程吗?
      • 我用过,但他们没有加入。当我只需要主线程时,我打印出活动线程的数量是 5。我怎样才能加入它们?
      • 抱歉,弄错了。您需要对主线程的引用,而不是调用 System.getCurrentThread().join(),而且无论如何它不会真正达到您的预期。使用 ExecutorService (download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…),它更容易,而且正是你想要的。
      • 我试过了,但它告诉我不能使用 Runnable。我的截止日期在 1.5 小时后结束,我很恐慌 :)
      • Join 只是导致当前线程等待线程对象 join 所代表的线程死亡。它并没有真正做任何事情。如果您想避免使用 ExecutorService,您可以使用 CountDownLatch (download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…),插入您拥有的下载器线程数。完成后,对闩锁进行倒计时。最终,锁存器将变为 0 并向等待线程(您的主线程)发出信号,这将继续......
      【解决方案6】:

      只需执行 2 个循环,首先将它们全部启动,然后将它们全部加入。

      【讨论】:

        【解决方案7】:

        对于我从您的问题中得到的解释,由于没有明确说明,您有多个下载器线程并希望同步它们。在这种情况下,您需要使用外部结构,例如 CountdownLatch。 CountdownLatch 是一种“同步辅助工具,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。”把它想象成一个计数器,每次下载线程完成时都会减少,当倒计时达到 0 时,所有线程都应该完成它们的工作。

        让我们看一些直接取自 CountdownLatch 官方 javadoc 的代码:

         CountDownLatch startSignal = new CountDownLatch(1);
         CountDownLatch doneSignal = new CountDownLatch(N);
        
         for (int i = 0; i < N; ++i) // create and start threads
             new Thread(new Worker(startSignal, doneSignal)).start(); 
        
         startSignal.countDown(); // Threads are waiting for startSignal's counter to reach 0 and start their work, more or less at the same time
         doneSignal.await(); // The current thread will sleep at this point until all worker threads have each one called doneSignal.countDown() and the countdown reaches 0
        
         joinDownloadedPieces(); // At this point is safe to assume that your threads have finished and you can go on with anything else    
        

        你的 worker Runnable/Thread 类看起来像这样:

        public void run() {
            startSignal.await(); // Wait until given the go
        
            downloadSomething();
        
            doneSignal.countDown(); // Notify that we are done here
        }
        

        我说可以安全地假设,因为在所有线程完成之前,您将不得不处理可能在等待期间唤醒您的线程的 InterruptedExceptions。有关更多信息,我建议您查看java.util.concurrent 包。恕我直言,它是 Java 的最佳瑰宝之一,文档详尽,通常足以理解每个结构的功能。

        【讨论】:

        • 我不知道,这对于像并行下载这样简单的事情来说似乎有点过分了。然后再次使用ExecutorService 将是避免在长时间运行的应用程序中创建和销毁许多线程的好主意。
        • @Neil 我同意,执行程序应该优先于直接使用线程。但是,Latch 用于线程同步,而不是管理它们的生命周期。
        • 是的,但是 OP 似乎没有任何要求同步线程的起点,即每个线程一旦准备好就可以启动。因此,您可以将其减少到只有一个 Latch。
        猜你喜欢
        • 1970-01-01
        • 2022-10-16
        • 1970-01-01
        • 1970-01-01
        • 2016-02-16
        • 1970-01-01
        • 1970-01-01
        • 2014-05-16
        • 1970-01-01
        相关资源
        最近更新 更多