【问题标题】:How to avoid both deadlocks and using too many threads?如何避免死锁和使用太多线程?
【发布时间】:2023-03-08 15:57:01
【问题描述】:

使用Executors.newFixedThreadPool(int nThreads) 是减少创建过多线程的开销的好方法,但如果所有线程都在等待另一个作业,而另一个作业本身正在等待池中的空闲线程,则它可能会导致死锁。有时问题可以通过使用多个线程池来解决,但有时却不能。我正在寻找行为类似于newFixedThreadPool 的东西,除非所有池线程都被阻塞 - 在这种情况下,尽管池有预定义的界限,但池应该会增长。 有这样的吗?


实际上,这里的死锁并不那么重要。真正的问题是“如何管理 running 线程的数量”而不是它们的总数。当试图在不创建不必要的线程的情况下充分利用 CPU 时,这也很有趣。

【问题讨论】:

    标签: java threadpool


    【解决方案1】:

    如果您有争用问题,这是一个设计问题。如果您想按照您的描述快速解决问题,您只会治愈症状,而不是潜在的疾病。

    您应该改用其他方式重构设计以消除死锁。

    【讨论】:

    • 一个好的开始是在等待有争议的资源时设置超时,这样如果资源没有被释放,这些线程可以做其他事情。然后找出为什么一个(另一个)线程持有该资源的时间过长——例如,它可能在异常情况下没有被正确释放。确保只在需要时才持有资源,绝对不再持有。
    • 我的设计没有死锁。只有一个(通常很小但)无限数量的工作可能在等待另一个工作。我真的不认为改变它会改善设计。
    • 那么也许你不应该在你的问题中使用“死锁”这个词......包括我在内的一些人将“死锁”解释为存在循环等待并且绝对没有的情况摆脱它的方法。添加抢占(即超时)、限制资源可以保留的时间等不仅可以解决您的问题,而且可以证明是一个更好的整体设计。
    • 死锁是真正的死锁,但只有在使用固定线程池时才会发生,因为这会使线程本身成为竞争资源。使用无限数量的线程没有问题,除了运行的线程太多。你的建议很好,但我不确定我是否可以在这方面进一步改进程序。
    【解决方案2】:

    让池中的线程阻塞等待同一个线程池中的其他线程通常是一个坏主意。

    我会尝试将设计更改为非阻塞设计。如果一个线程需要同一个执行器正在处理的另一个操作的结果,我会让它向执行器提交一个任务,以便在第二个操作完成后运行。或者将一个对象放入队列中,以便稍后在其他作业完成时提取。

    或者,您可以像 Swing 对模态对话框所做的那样,让即将阻塞的线程启动一个子线程来继续处理请求,直到父线程解除阻塞。但是,这很难做到,并且需要您手动管理线程,这比使用 Executor 安全性要低得多。

    【讨论】:

    • +1 切换到非阻塞设计肯定是个好主意,但是,它可能会变得复杂,因为它有时意味着自己模拟线程。此外,在某些情况下,线程可能会在您无法控制甚至不知道的地方被阻塞。正如您所写,另一个想法可能会变得复杂。尽管如此,这显然是迄今为止最好的答案。
    【解决方案3】:

    Executor.newCachedThreadPool(); 缓存线程池将检查是否有任何可用线程。如果有,线程池将重新使用该线程。如果不是,线程池将创建一个新线程。线程的生存时间是 60 秒,因此 60 秒后额外的线程将被终止。

    【讨论】:

    • 不幸的是,这种解决方案不仅会在出现死锁情况时创建新线程,而且还会在线程正在运行但同时提交新线程的情况下创建新线程。因此,如果您同时启动大量任务,您将创建一个巨大的线程池。
    • @Howard 是的,这正是我的观点。我会很高兴有一个池以某种方式限制 running 线程的数量,而不是 all 线程的数量。
    • @Howard 考虑到每个算法来找出是否存在死锁(在大图中查找循环)都会非常昂贵,因此使用某种简单的启发式算法确实是唯一的方法..
    • @Voo 我在考虑线程由于某种原因被阻塞,而不是死锁的线程。你可以有数千个任务,所以我认为循环查找应该是相当微不足道的。在任何时候,每个线程最多可能被一个线程阻塞。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-11
    • 1970-01-01
    相关资源
    最近更新 更多