【问题标题】:Java: Is `while (true) { ... }` loop in a thread bad? What's the alternative?Java:线程中的“while (true) { ... }”循环是否不好?有什么选择?
【发布时间】:2011-03-22 23:38:07
【问题描述】:

线程中的while (true) { ... } 循环坏了吗?有什么办法?

更新;我想要...

我有大约 10,000 个线程,每个线程都使用来自其私有队列的消息。我有一个线程一一生成消息并将它们放入正确的消费者队列中。每个消费者线程无限循环,检查是否有消息出现在他们的队列中并处理它。

Consumer.java 内部:

@Override
public void run() {
    while (true) {
        Message msg = messageQueue.poll();
        if (msg != null) {
            ... // do something with the message
        }
    }
}

生产者正在快速将消息放入消费者消息队列中(每秒数百万条消息)。消费者应尽快处理这些消息!

注意:while (true) { ... } 被 Producer 作为其最后一条消息发送的 KILL 消息终止。但是,我的问题是关于进行此消息传递的正确方法...

有关此设计,请参阅the new question

【问题讨论】:

  • 你为什么不提供更多关于你想要达到的目标的细节,我们会告诉你是否有更好的方法来做到这一点。
  • @Ryan: 已更新;请看一下!
  • 10000 个线程似乎是个坏主意,因为每个线程都可能存在开销...
  • @Colen: 请看我的新问题,因为我也认为这也是个坏主意......还有什么选择? stackoverflow.com/questions/3367383/…
  • 10k线程的开销会很大

标签: java multithreading concurrency loops message-passing


【解决方案1】:

通常,您会希望在某种资源上wait 来完成工作,这会对您隐藏实际的线程细节。听起来您想实现自己的spinlock

Here's some tutorial about locking I found I google.

【讨论】:

    【解决方案2】:
    while (!stop_running) { ... }
    

    ...也许吧?某种退出标志通常用于控制线程运行。

    【讨论】:

    • 如果您这样做,请确保 stop_running 使用 volatile 关键字。请参阅 Java 并发实践,第 7.1 节。
    • 这看起来很简单,但问题是:stop_running 在循环开始后如何变为 false?这就是我认为大多数人想知道的
    【解决方案3】:

    不是天生的,不。您可以随时使用breakreturn 进行保释。只要确保你确实做到了(在某个时候)

    问题是当你的线程无事可做时会发生什么?如果你只是循环检查一个条件,你的线程会吃掉整个 CPU 什么都不做。因此,请确保使用 wait 导致您的线程阻塞,或者使用 sleep 如果您对 wait 没有任何要求。

    【讨论】:

    • 真的有那么多CPU吗?
    • @Clocker - 自己尝试一下,但是是的,我相信繁忙的等待会占用一个 CPU 内核的 100%。
    • @Clocker 这被称为 busy loop 并使用它可以从单个内核获得的所有 cpu 功率。在过去,cpu 只有一个核心,这可能会使您的系统崩溃,因此开发人员从两个 cpu 系统中受益匪浅。
    【解决方案4】:

    取决于“坏”的定义。这意味着试图阅读代码的人必须在别处寻找循环终止的原因。这可能会降低它的可读性。

    这种极端的心态导致了 COMEFROM 关键字。 http://en.wikipedia.org/wiki/COMEFROM

    10 COMEFROM 40
    20 INPUT "WHAT IS YOUR NAME? "; A$
    30 PRINT "HELLO, "; A$
    40 REM
    

    【讨论】:

      【解决方案5】:

      最好在while (...) 行上有终止条件,但有时终止条件是您只能在循环内部某处测试的东西。这就是break 的用途(或例外)。事实上,也许你的线程必须永远运行,直到你的程序终止(使用System.exit);那么while (true)肯定是对的。

      但也许你在问循环内部应该做什么。您需要确保包含一些阻塞操作,即某些函数调用,您的线程将在其中等待其他人(另一个线程、另一个程序、操作系统)做某事。这通常是Condition.wait,如果您正在使用锁进行编程,或者从消息队列中读取,或者从文件或网络套接字中读取,或者其他一些阻塞 I/O 操作。

      请注意,sleep 通常不够足够好。您无法知道其他参与者何时会做某事,因此无法避免醒来太频繁(从而不必要地消耗 CPU 时间)或太少(因此无法及时对事件做出反应)。始终设计您的系统,以便当线程完成其工作时,它会通知正在等待该工作的任何人(通常使用Condition.signal 或通过加入)。

      【讨论】:

        【解决方案6】:

        您可以选择检查中断状态,而不是永远循环并中断或返回。

        while (!Thread.currentThread().isInterrupted()) {
            try {
                doWork();
                wait(1000);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
        

        如果您的线程是由 ExecutorService 管理的任务,您可以通过调用 shutdownNow() 让它们全部正常结束。

        【讨论】:

        • 使用 Thread.sleep(1000) 比使用 wait() 更好,cz wait 需要先阻塞对象。
        • 嘿。很好...但是如果我想从观察者的update 方法中杀死线程怎么办...我不知道触发该方法需要多长时间,所以随机的wait(1000) 将不起作用这里....
        【解决方案7】:

        我通常使用一个名为'done'的类属性布尔值,然后线程的运行方法看起来像

        done = false;
        while( !done ) {
            // ... process stuff
        }
        

        然后您可以设置 done=true 来终止循环。这可以在循环内部完成,或者您可以使用其他方法设置它,以便其他线程可以拔出插头。

        【讨论】:

          【解决方案8】:

          while (true) 不错,如果有办法退出循环,否则调用将无限期运行。

          对于 10000 个线程执行 while(true) 调用是不好的做法...为什么不在线程上设置 sleep() 以允许其他线程运行或在线程完成运行时退出策略?

          【讨论】:

            【解决方案9】:

            如果我按照你说的做,我会试试这个:

            private Object lock = new Object();    
            
            public void run(){
                while(true){
                    synchronized(lock){
                        Message msg = messageQueue.poll();
                        if (msg != null) {
                            ... // do something with the message
                        }else{
                            try{
                                lock.wait();
                            }catch(InterruptedException e){
                                e.printStackTrace();
                                continue;
                            }
                        }
                    }
                }
            }
            

            这使您可以确保不会在 messageQueue 上获得任何并发修改异常,以及当没有消息时您不会在 while(true) 循环中使用 CPU 时间。现在你只需要确保当你添加一些东西到你的 messageQueue 时你可以调用lock.notifyAll() 以便线程知道再次运行。

            【讨论】:

            • 共享队列不是很好。
            • 对 messageQueue 使用 LinkedBlockingQueue 并且它将是线程安全的,不需要上面的锁或同步块。如果您想在队列为空时阻塞,只需调用 take 而不是 poll 。如果你想实现超时,你也可以使用 poll 和超时。
            • 吞下中断的异常意味着你的线程永远不会退出,除非发生 RuntimeException。
            【解决方案10】:

            假设您是标准BlockingQueue,您似乎正忙着等待。使用take 而不是poll

            除此之外,for (;;)while (true),IMO 更好。

            【讨论】:

            • 使用 take 会一直等待消息的到来,对吗?而 poll(100) 将等待 100 毫秒并继续执行。那么,poll(100) 不是比 take 更好吗?
            【解决方案11】:

            首先,Dough Lea 对这个问题的直接回答:

            使用裸旋转等待变量值几乎从来都不是一个好主意。使用 Thread.onSpinWait、Thread.yield 和/或阻塞同步来更好地应对“最终”可能需要很长时间这一事实,尤其是当系统上的线程数多于内核数时。

            http://gee.cs.oswego.edu/dl/html/j9mm.html

            Thead.onSpinWait 是在 Java 9 中引入的。它可能看起来像这样。

            while (true) {
                while (messageQueue.peek() == null) {
                   Thread.onSpinWait();
                }
                // do something with the message
            }
            

            通过在自旋等待循环构造的每次迭代中调用此方法,调用线程向运行时指示它正忙于等待。运行时可能会采取措施来提高调用自旋等待循环构造的性能。

            https://docs.oracle.com/javase/9/docs/api/java/lang/Thread.html#onSpinWait--

            【讨论】:

              【解决方案12】:

              虽然上述所有答案都是正确的,但我想建议这个答案,因为我自己也遇到了这种情况: 你可以用一个标志说:

              isRunning=true;
              while(isRunning){
                 //do Something
              }
              

              稍后,在完成从缓冲区或数据文件的读取后,确保将 isRunning 设置为 false。

              【讨论】:

                猜你喜欢
                • 2021-04-02
                • 1970-01-01
                • 1970-01-01
                • 2021-02-09
                • 2013-06-28
                • 1970-01-01
                • 2016-12-03
                • 1970-01-01
                • 2015-06-21
                相关资源
                最近更新 更多