遇到以下代码(经过修改以明确我的观点):
class ThreadWithQueue extends Thread {
Queue<Msg> msgQueue;
public void run() { while (!isInterrupted()) {processMessage(queue.take());}
}
class MessageDispatcher {
ThreadsWithQueue[] threads;
...
void dispatchMessage(Msg msg) {
int index = findMatchingThread(msg);
if (!threads[index].isAlive() || threads[index].isInterrupted()){
try {
threadCreateLock.lock();
threads[index] = createAndStartThread();
} finally {
threadCreateLock.unlock();
}
}
threads[index].queue.add(msg);
}
也就是说,有一个&#39;消息调度程序&#39;如果发现当前线程已死或中断,则可以重新启动(即创建一个新线程)。
请注意,这两个类都是由同一个作者编写的,并且位于同一个包中。
我的问题:
这有什么意义吗?重新启动一个死线程&#39;从设计/最佳实践的角度来看功能?
如果只有 捕获(Throwable){/ *忽略 /} * &#39;这不是一个更好的方法。在每个线程的 run()方法内(即内部&#39; ThreadWithQueue.run(){...} &#39;)?
答案 0 :(得分:2)
这有什么意义吗?重启一个死线程&#39;功能 从设计/最佳实践的角度来看?
请注意,如果旧的线程已死或中断,您提供的代码将启动一个新线程。虽然这可能是由于线程的run()
抛出一个意外的异常,但如果它被中断,特定线程也会自然死亡。后一种行为是提供关闭系统的典型方式。
排队代码看起来很自然。当向它显示一条消息时,它确保选择处理它的线程既不会死也不会在它实际调度消息之前死掉。
不会更好 在每个内部只有
catch(Throwable) {/*ignore*/}
的方法 线程的run()
方法(即在ThreadWithQueue.run() {...}
内)?
不是真的。首先,对所有类型的Throwable
进行全面捕捉和忽略政策是一个完全不好的主意。更具体地说,您提出的建议将阻止未被捕获的异常处理程序参与处理此类异常。第二,如上所述,未捕获的异常并不是导致其中一个线程死亡的唯一因素,因此您提出的建议不会减轻消息调度程序必须测试活动并在其启动新线程时找到需要。
答案 1 :(得分:1)
通常,在异常情况下,当您不知道处理代码是否已正确清理时,重新启动线程可能很有用,而不是在捕获并报告异常后继续。最值得注意的是,只要线程处于活动状态,就可能有ThreadLocal
个变量具有悬挂的关联值。
但是,这里有一些奇怪的事情。使用isInterrupted()
作为重启的标准没有多大意义,因为前面的测试证明线程仍然存在。所以情况可能是这样,在processMessage
内的某个地方将使用和处理中断,在返回主循环时被重置。所以最后,你会有两个正在运行的线程,其中一个在队列为空时永远挂在queue.take()
中。可能是您可以排除当前代码processMessage
的情况,但是,您最好停止任何代码开发,以确保将来不会激活该定时炸弹。
此外,这里有几种可能的竞争条件。在检查了 isAlive()
和isInterrupted()
后,线程可能会立即死亡,因此您仍然会加入死线程。但由于无论如何都没有尝试将死线程的待处理消息传递给新线程,因此当线程因异常而死亡时,任意数量的消息都可能丢失。即使在dispatchMessage()
内存在这样的转移,也意味着在线程终止之后,在新线程开始处理悬空队列之前,直到尝试将新消息调度到同一线程为止。显然,如果队列不为空,则应该由异常处理程序触发生成新线程,避免这种延迟。
兔子洞更深入。虽然threads[index]
是在threadCreateLock
下写的,但是在没有保持锁定的情况下读取它,这是一个破坏的同步。无法保证数组元素更新的可见性。线程可以读取过时的引用,创建一个新线程,尽管另一个线程已经创建了一个。更糟糕的是,一个线程可以读取一个新的引用,该引用尚未被持有该锁的线程完全初始化。如果msgQueue
确实不是final
,就像您发布的那样,在这种情况下,某个帖子可能会遇到null
引用。
我强烈建议使用fixed thread pool,它具有发布代码试图实现的功能,但是由专家编写,了解多线程的这些缺陷。