【问题标题】:Why can't a Java Thread object be restarted?为什么不能重新启动 Java Thread 对象?
【发布时间】:2010-10-27 14:12:50
【问题描述】:

我知道无法重新启动使用过的 Java Thread 对象,但我找不到解释为什么这是不允许的;即使保证线程已经完成(参见下面的示例代码)。

我不明白为什么 start()(或至少是 restart())方法不能以某种方式将 Thread 对象的内部状态(无论它们是什么)重置为与 Thread 对象相同的值对象是新创建的。

示例代码:

class ThreadExample {

  public static void main(String[] args){

    Thread myThread = new Thread(){
      public void run() {
        for(int i=0; i<3; i++) {
          try{ sleep(100); }catch(InterruptedException ie){}
          System.out.print(i+", ");
        }
        System.out.println("done.");
      }
    };

    myThread.start();

    try{ Thread.sleep(500); }catch(InterruptedException ie){}
    System.out.println("Now myThread.run() should be done.");

    myThread.start(); // <-- causes java.lang.IllegalThreadStateException

  } // main

} // class

【问题讨论】:

  • 我的多线程技能不是太高级,但据我所知,没有(真正的)保证您的线程将在 500 毫秒内完成。它可能最终具有低优先级和等待,或者在某些更高级的情况下更糟的是锁定。话虽如此,我也认为这是一个有趣的问题,为什么不能重新启动状态为 TERMINATED 的线程。
  • @posdef:示例代码只是为了演示问题。可以使用更精细的机制,等待myThread.run() 确定完成(或者接受在极少数情况下会引发异常)。
  • 我不知道您在任何系统中可以重新启动线程。

标签: java multithreading


【解决方案1】:

我知道这是不可能的 重新启动一个使用过的 Java Thread 对象,但是 我找不到解释为什么 不被允许;即使是 保证线程有 完成(参见下面的示例代码)。

我的猜测是线程可能直接绑定(出于效率或其他限制)到实际的 native 资源,这些资源在某些操作系统中可能是可重新启动的,但在其他操作系统中则不是。如果 Java 语言的设计者允许重新启动线程,他们可能会限制 JVM 可以运行的操作系统数量。

想一想,我想不出一个操作系统允许线程或进程在完成或终止后重新启动。当一个进程完成时,它就会死亡。你想要另一个,你重新启动它。你永远不会复活它。

除了底层操作系统带来的效率和限制问题之外,还有分析和推理的问题。当事物是不可变的或具有离散的、有限的生命周期时,您可以推断并发性。就像状态机一样,它们必须有一个终端状态。开始、等待、结束了吗?如果你允许线程复活,这样的事情就不容易推理了。

您还必须考虑复活线程的含义。重新创建它的堆栈,它的状态,是否可以安全地复活?你能复活一个异常结束的线程吗?等等。

毛茸茸的,太复杂了。所有这些都是为了微不足道的收益。最好将线程保留为不可恢复的资源。

【讨论】:

    【解决方案2】:

    我会反过来提出问题 - 为什么应该 Thread 对象可以重新启动?

    可以说,推理(并且可能实现)一个简单地执行其给定任务一次然后永久完成的线程要容易得多。要重新启动线程,需要更复杂地了解程序在给定时间所处的状态。

    因此,除非您能想出一个具体的原因,为什么重新启动给定的Thread 比创建一个具有相同Runnable 的新选项更好,否则我认为设计决策是更好的选择。

    (这与关于 mutable 与 final 变量的争论大体相似——我发现最终的“变量”更容易推理,并且更愿意创建多个新的常量变量而不是重用现有的变量。)

    【讨论】:

    • restart 渴望的一个原因是创建新的(和垃圾收集旧的)线程对象的成本。
    • 然后只需使用一个线程池,其中每个线程都可以跨多个 Runnable 重用。
    • 正如史蒂夫所说,使用线程池机制。我喜欢可以通过 Executors 类的静态方法获得的 ExecutorService 实现的易用性。使用 runnables 作为可重新启动的任务。
    • @Curd - 您是否分析过您的应用程序并发现对象分配和线程 GC 的成本构成了不可忽略的运行时成本?除非你在做某事疯狂,否则它几乎肯定不会。而在这种情况下,只需将Runnables 发送到线程池(如ThreadPoolExecutor)即可。
    【解决方案3】:

    因为他们不是这样设计的。从清晰的角度来看,这对我来说很有意义。一个线程代表一个执行线程,而不是一个任务。当那个执行线程完成时,它已经完成了它的工作,如果它再次从顶部开始,它只会把事情弄得一团糟。

    另一方面,Runnable 代表一个任务,可以根据需要多次提交给多个线程。

    【讨论】:

      【解决方案4】:

      您为什么不想创建一个新线程?如果您担心创建 MyThread 对象的开销,请将其设为 Runnable 并使用 new Thread(myThread).start(); 运行它

      【讨论】:

      • 一旦 runnable 的 run() 方法结束(或突然结束),这仍然会产生 同一线程创建开销。避免这种开销的方法是创建线程池或自定义线程类等到它们获得带有Runnable(或其他类似命令的对象)的消息才能运行。一旦结束,封闭的线程就会进入睡眠状态,直到它获取/接收另一个线程。或者消费者自定义线程池使用队列中的Runnable 对象。
      • 这仍然需要创建新的(和旧的垃圾回收)线程对象。
      • @luis.espinal:好点。你为什么不写这个作为答案呢?
      • @Curd:不,不是 luis.espinal 描述的方式。实际线程(即实例java.lang.Thread)永远不会终止,它只是在完成后等待,直到它收到一个新的Runnable。只有Runnables 被创建和GCed,这些可以是你想要/需要的轻量级。
      【解决方案5】:

      Java 线程遵循基于下面状态图的生命周期。一旦线程处于最终状态,它就结束了。这就是设计。

      【讨论】:

      • 这很清楚,但它设计成这样的原因是什么?
      【解决方案6】:

      您可以通过使用java.util.concurrent.ThreadPoolExecutor 来解决这个问题,或者手动通过在给定的每个Runnable 上调用Runnable.run() 的线程,而不是在完成时实际退出。

      这不是您要问的,但如果您担心线程构建时间,那么它可以帮助解决这个问题。以下是手动方法的一些示例代码:

      public class ReusableThread extends Thread {
          private Queue<Runnable> runnables = new LinkedList<Runnable>();
          private boolean running;
      
          public void run() {
              running = true;
              while (running) {
                  Runnable r;
                  try {
                      synchronized (runnables) {
                          while (runnables.isEmpty()) runnables.wait();
                          r = runnables.poll();
                      }
                  }
                  catch (InterruptedException ie) {
                      // Ignore it
                  }
      
                  if (r != null) {
                      r.run();
                  }
              }
          }
      
          public void stopProcessing() {
              running = false;
              synchronized (runnables) {
                  runnables.notify();
              }
          }
      
          public void addTask(Runnable r) {
              synchronized (runnables) {
                  runnables.add(r);
                  runnables.notify();
              }
          }
      }
      

      显然,这只是一个例子。它需要更好的错误处理代码,也许还有更多可用的调整。

      【讨论】:

        【解决方案7】:

        如果您担心创建新 Thread 对象的开销,那么您可以使用执行器。

        import java.util.concurrent.Executor;
        import java.util.concurrent.Executors;
        public class Testes {
            public static void main(String[] args) {
                Executor executor = Executors.newSingleThreadExecutor();
                executor.execute(new Testes.A());
                executor.execute(new Testes.A());
                executor.execute(new Testes.A());
            }   
            public static class A implements Runnable{      
                public void run(){          
                    System.out.println(Thread.currentThread().getId());
                }
            }
        }
        

        运行这个你会看到同一个线程被用于所有的 Runnable 对象。

        【讨论】:

          【解决方案8】:

          Thread 不是 线程thread 是代码的执行。 Thread 是您的程序用来创建和管理 线程 生命周期的对象。

          假设你喜欢打网球。假设你和你的朋友玩了一套非常棒的游戏。如果你说“太棒了,让我们再玩一次”,你的朋友会有什么反应。你的朋友可能会认为你疯了。甚至谈论再次玩同一组也没有意义。如果您再次演奏,您将演奏不同的组。

          线程 是您的代码的执行。甚至谈论“重用”执行线程是没有意义的,原因与谈论在网球中重玩同一组毫无意义的原因相同。即使您的代码的另一次执行以相同的顺序执行所有相同的语句,它仍然是一个不同的执行。

          Andrzej Doyle 问道:“你为什么要想要重复使用Thread?”为什么确实如此?如果Thread 对象代表一个执行线程——你甚至不能谈论重用的短暂事物——那么你为什么想要或期望Thread 对象可以重复使用吗?

          【讨论】:

            【解决方案9】:

            我一直在寻找您似乎正在寻找的相同解决方案,并以这种方式解决了它。如果发生 mousePressed 事件,您可以终止它也可以重用它,但它需要初始化,如下所示。

            class MouseHandler extends MouseAdapter{
                public void mousePressed(MouseEvent e) {            
                    if(th.isAlive()){
                        th.interrupt();
                        th = new Thread();
                    }
                    else{
                        th.start();
                    }
                }
            
            }
            

            【讨论】:

            • 这实际上是在创建一个新线程而不是重置旧线程对象。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-09-05
            • 2023-03-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-08-28
            • 1970-01-01
            相关资源
            最近更新 更多