对线面试官 - 线程池 (三)
面试官:线程池在实际工作中是如何使用的?例如,线程池中的核心线程数如何确定?
派大星:嗨,面试官!线程池在实际工作中被广泛应用。它可以管理和复用线程,提高程序的性能和效率。核心线程数的设置主要取决于几个因素,包括 CPU 核数、机器内存、IO 支持的最大 QPS 以及任务类型。
面试官:那应该将线程池的核心线程数设置为多大呢?
派大星:根据以往的经验,对于 CPU 密集型任务,核心线程数应该等于机器的核数加一。这样可以充分利用多核 CPU 的计算能力,并保留一个额外的线程用于处理突发任务。对于 IO 密集型任务,核心线程数应该设置为两倍的 CPU 核数,因为 IO 操作通常需要较多的等待时间,可以利用多个线程同时处理。
面试官:如果任务是复合型的,既包含 CPU 密集型任务又包含 IO 计算,如何设置核心线程数呢?假设在一个请求中,计算操作需要 5ms,DB 操作需要 100ms。对于一台有 8 核的机器,如果要求 CPU 利用率达到 100%,应该如何设置线程数?
派大星:对于复合型任务,我们可以综合考虑 CPU 密集型和 IO 密集型的特点进行设置。在这种情况下,如果计算操作需要 5ms(CPU 操作 5ms),而 DB 操作需要 100ms,那么单核 CPU 的利用率就是 5/(5+100)。为了达到理论上的 100% CPU 利用率,需要的线程数为 1/(5/(5+100)),即 21 个线程。然而,现在我们只有 8 个核可用,所以根据这个理论,需要的线程数是 168 个(8 核 * 21 线程)。不过,这是一个理论值,实际情况可能受其他因素的限制。
面试官:如果 DB 操作的最大 QPS 是 1000,应该设置多少核心线程数呢?
派大星:为了保持相对比例,可以根据比例减少线程数。如果有 168 个线程,那么 DB 的访问 QPS 将达到 1600(168 * (1000/(5+100)))。然而,由于 DB 的最大 QPS 只能是 1000,我们需要按比例减少线程数。因此,核心线程数的大小应该是 105 个线程(168 * 1000/1600 = 105)。
面试官:如何捕获线程池中的异常呢?
派大星:有几种方法可以捕获线程池中的异常。一种方法是通过手动使用 try-catch 块来捕获异常并打印出来,但这样的写法比较繁琐和不够优雅。
另一种方法是利用 Thread 类中的 dispatchUncaughtException(Throwable e)方法。当线程抛出异常时,JVM 最终会回调这个方法来进行最后的异常处理,而且该异常会被 ThreadGroup 类中的 uncaughtException 方法处理。我们可以在创建 Thread 对象时绑定一个自定义的异常捕获处理器,最终发生异常时会打印我们的错误日志。下面是一个示例代码:
然而,在项目中我们更常使用线程池而非单独的线程。线程池中的线程对象实际上是由线程工厂创建的。我们可以在线程工厂中设置一个异常捕获处理器。以下是使用 ThreadPoolExecutor 创建线程池时设置线程工厂的示例代码:
但是在使用 Spring 的线程池时,由于其线程工厂无法设置任何值,我们可以采用装饰器模式。我们将 Spring 的线程池线程工厂传入装饰器中,并调用其创建线程的方法。然后,我们添加我们自定义的异常捕获处理器。在使用线程池时,我们替换掉 Spring 的线程工厂,并将本类的线程工厂进行包装传递进去,从而实现线程池的异常捕获。以下是具体实现方式的示例代码:
面试官:非常清楚和详细的解释,谢谢你的分享。你的回答对于捕获线程池中的异常提供了多种方法和具体实现。
如有问题,欢迎加微信交流:32479732,或关注微信公众号【码上遇见你】。
版权声明: 本文为 InfoQ 作者【派大星】的原创文章。
原文链接:【http://xie.infoq.cn/article/958b4ef2a959cfc2c5ff59762】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论