为什么不能重新启动Java Thread对象?

我知道不可能重新启动使用过的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 

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

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

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

除了底层操作系统强加的效率和限制问题外,还存在分析和推理的问题。 当事物是不可变的或具有离散的,有限的生命周期时,你可以推理出并发性。 就像状态机一样,它们必须具有终端状态。 是开始,等待,完成? 如果允许Threads复活,那么这样的事情就不容易被推理。

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

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

我反过来提出问题 – 为什么Thread对象可以重启?

可以说更容易推理(并且可能实现)一个只执行一次给定任务的线程,然后永久完成。 要重新启动线程,需要更复杂地查看程序在给定时间处于什么状态。

因此,除非你能够提出一个特定的原因,为什么重新启动一个给定的Thread是一个更好的选择,而不仅仅是创建一个具有相同Runnable的新的,我认为设计决策是更好的。

(这与关于可变变量和final变量的论证大致类似 – 我发现最终的“变量”更容易推理,而更愿意创建多个新的常量变量,而不是重用现有变量。)

因为他们没有这样设计。 从清晰的角度来看,这对我来说很有意义。 线程表示执行线程,而不是任务。 当那个执行线程已经完成时,它已经完成了它的工作而且只是混乱的事情是它再次从顶部开始。

另一方面,Runnable表示任务,可以根据需要多次提交给许multithreading。

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

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

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

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

 public class ReusableThread extends Thread { private Queue runnables = new LinkedList(); 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(); } } } 

显然,这只是一个例子。 它需要具有更好的error handling代码,并且可能需要更多调优。

如果您担心创建新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对象使用相同的线程。

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

假设你喜欢打网球。 假设你和你的朋友玩得非常棒。 如果你说的话,你的朋友会如何反应,“这太不可思议了,让我们再玩一次吧。” 你的朋友可能认为你疯了。 即使谈论再次播放同一组也没有意义。 如果你再玩一次,你会玩一套。

线程是代码的执行。 即使谈论“重新使用”执行线程也是没有意义的,因为在网球中重新演奏相同的设置是没有意义的。 即使代码的另一次执行以相同的顺序执行所有相同的语句,它仍然是一个不同的执行。

Andrzej Doyle问道,“你为什么重新使用一个Thread ?” 为什么呢? 如果一个Thread对象代表一个执行的线程 – 一个你甚至无法谈论重新使用的短暂事物 – 那么为什么你想要或期望Thread 对象可以重用?

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

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