总结:我正在尝试使用ThreadPoolExecutor并行化我的系统,而且我遇到了死锁问题。我知道为什么我会遇到这个问题,但是我对java的上下文并不十分熟悉,所以我不确定要使用哪些类/方法:
简单示例:我的代码解析一个类似xml的大型树,然后为顶级指令生成一个线程。这个线程运行;当它执行时,它会读取更多的xml树并在同一个ThreadPoolExecutor上生成更多线程(我使用1个池,因为有一个用户定义的最大值)。这种情况发生在xml树被消耗之前。
当足够的父线程在线程池中结束并且它们的所有子线程仍然在队列中时,它们会死锁(因为父线程在其子线程上被阻塞,它们没有运行)。假设我的树是
Parent thread
Child Thread A
Child Child Thread A1
Child Child Thread A2
Child Child Thread A3
Child Thread B
Child Thread C
我现在将父线程A,B,C,A1,A2和A3作为线程。假设我的最大线程池是2.这意味着在解析它的方式中,P,A在线程池中,而其他所有内容都在线程队列中。
这里,每个非叶节点都将在叶节点上被阻塞。由于叶节点最后被解析,它们甚至不在线程池中(而是在队列中)。因此叶子儿从不跑,整个系统陷入僵局。
问题:一旦创建子线程,是否有一些方法调用将父线程踢出正在运行的线程池?我担心使用回调因为我的系统可以选择性地启动并发,所以我真的不想在子类上混淆代码以回调甚至可能不存在的父线程。
另外,我应该如何实现某种线程队列拣选算法(优先级/时钟/等); ThreadPoolExecutor是正确使用的类吗?
感谢。
答案 0 :(得分:2)
如果您需要等待子线程完成直到完成工作的“等待线程”,请查看java.util.concurrent.ForkJoinPool
。它允许您从父任务中生成子任务。虽然父任务必须等待,但它们不会阻止任何线程。
任务本身是通过子类java.util.concurrent.RecursiveTask
实现的。您还可以在JavaDoc中找到一个代码示例。
答案 1 :(得分:0)
你的初始问题的答案是,我认为,“不” - 如果你正在使用线程池,除了从run()方法返回之外,你不能“回放”一个线程回到池中。线程池限制为线程总数,而不是“正在运行”线程的总数。
我建议不要在同一个池中的其他任务中使用池块中的线程,这正是您遇到的原因。如果你需要按照这些方法做一些事情,你可能想要使用单独的ThreadPools(更容易),或者重构你的代码,这样,子任务代替子任务上的父阻塞,在完成后通过回调调用父代
答案 2 :(得分:0)
您可以通过调用Executors.newCachedThreadPool()
来使用无界线程池执行程序。实际上,这将始终允许孩子开始跑步,因为如果需要,将创建新的 线程。正如Sbodd所注意到的那样,由于你不必要地占用了父线程的资源,因此在孩子身上使用父块并不是一个好主意,因此我不推荐这种方法。
如果您的体系结构是真正的层次结构和树形,使用像Akka这样的东西可能非常适合您的问题,尽管它们的学习曲线非常陡峭。
或者,您可以尝试使用Google Guava listenable futures。我有一个blog post显示一个ListeningExecutorService
的示例,它允许您在完成任务(可调用/可运行)后注册回调。我相信Guava执行器远远优于普通的java版本,并且注册回调的能力允许以非常优雅的方式解决问题,就像你所描述的那样(让人联想到Scala或其他FPL中使用的未来功能样式)。
答案 3 :(得分:0)
听起来你对线程池的作用感到困惑;父进程生成子线程然后阻塞,等待子进程完成,仍在用完池中的一个线程。我可能不在这里,但你的问题的潜台词让我认为你正在考虑线程池,如同时运行线程的'配额',或者像系统中可用CPU的数量;换句话说,我怀疑你相信如果一个执行程序线程被阻塞,它就会被换掉而另一个取代它。如果你相信这一点,那么你就不明白为什么会出现这种僵局。
从操作系统的角度来看,这是真的;如果有可运行的线程,则始终运行某些内容。但是从线程池的角度来看,N个线程完全有可能被阻塞,并且CPU继续做其他人的工作。当你的一个父母生成一个孩子然后阻塞时,会冻结N个可用池线程中的一个;它不仅仅是把它放在后面,而是以操作系统的方式处理其他事情。一个无界的线程池可能会按你想要的方式运行(假设你的内存不足等),但是你可能想重新考虑你的架构,把“工作”放在一个工作队列上,让N个线程能够工作在工作。您可以将PriorityBlockingQueue
实现与自定义比较器一起使用,以确保正确的作业具有优先级。