写点什么

对线面试官 - 线程池 (三)

作者:派大星
  • 2023-06-06
    辽宁
  • 本文字数:2228 字

    阅读完需:约 7 分钟

面试官:线程池在实际工作中是如何使用的?例如,线程池中的核心线程数如何确定?


派大星:嗨,面试官!线程池在实际工作中被广泛应用。它可以管理和复用线程,提高程序的性能和效率。核心线程数的设置主要取决于几个因素,包括 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 对象时绑定一个自定义的异常捕获处理器,最终发生异常时会打印我们的错误日志。下面是一个示例代码:

public static void main(String[] args) {    Thread thread = new Thread(() -> {        log.info("------- info -------");        throw new RuntimeException("运行时异常~~~~~");    });    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = (t, e) -> {        log.error("Exception in Thread..... ", e);    };    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);    thread.start();}
复制代码

然而,在项目中我们更常使用线程池而非单独的线程。线程池中的线程对象实际上是由线程工厂创建的。我们可以在线程工厂中设置一个异常捕获处理器。以下是使用 ThreadPoolExecutor 创建线程池时设置线程工厂的示例代码:

private static ExecutorService executor = new ThreadPoolExecutor(1, 1,        0L, TimeUnit.MILLISECONDS,        new LinkedBlockingQueue<Runnable>(500),        new NamedThreadFactory("refresh-ipDetail", (ThreadGroup)null,false,                new GlobalUncaughtExceptionHandler()));
复制代码


@Slf4jpublic class GlobalUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {    @Override    public void uncaughtException(Thread t, Throwable e) {        log.error("Exception in thread {} ", t.getName(), e);        e.printStackTrace();    }}
复制代码

但是在使用 Spring 的线程池时,由于其线程工厂无法设置任何值,我们可以采用装饰器模式。我们将 Spring 的线程池线程工厂传入装饰器中,并调用其创建线程的方法。然后,我们添加我们自定义的异常捕获处理器。在使用线程池时,我们替换掉 Spring 的线程工厂,并将本类的线程工厂进行包装传递进去,从而实现线程池的异常捕获。以下是具体实现方式的示例代码:

@Slf4j@AllArgsConstructorpublic class MyThreadFactory implements ThreadFactory {    private ThreadFactory factory;
@Override public Thread newThread(Runnable r) { Thread thread = factory.newThread(r); thread.setUncaughtExceptionHandler(new GlobalUncaughtExceptionHandler()); thread.setDaemon(false); thread.setPriority(5); return thread; }}
复制代码


@Beanpublic ThreadPoolTaskExecutor websocketExecutor() {    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();    executor.setCorePoolSize(16);    executor.setMaxPoolSize(16);    executor.setQueueCapacity(1000);    executor.setThreadNamePrefix("websocket-executor-");    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());    executor.setThreadFactory(new MyThreadFactory(executor));    executor.initialize();    return executor;}
复制代码

面试官:非常清楚和详细的解释,谢谢你的分享。你的回答对于捕获线程池中的异常提供了多种方法和具体实现。


如有问题,欢迎加微信交流:32479732,或关注微信公众号【码上遇见你】。

发布于: 刚刚阅读数: 4
用户头像

派大星

关注

微信搜索【码上遇见你】,获取更多精彩内容 2021-12-13 加入

微信搜索【码上遇见你】,获取更多精彩内容

评论

发布
暂无评论
对线面试官-线程池(三)_Java 面试_派大星_InfoQ写作社区