深度思考线程池面经之三:线程

6 线程(阿里健康二面)

6.1 你线程池里面线程的数量和表的数量是什么关系呢?

答:当时想的是核心线程数设置为20,这是根据公式(核心线程数=cpu核数(线程等待时间/线程执行任务的平均时间+1)得来的,最大线程数设置表的数量,然后再使用一个阻塞队列,这里的非核心线程的数量也不敢设置的太大,因为数据同步涉及到大量的数据迁移任务,所以如果同步的过程中同时从mysql中拉取了太多的数据时,可能会造成OOM;

如果待同步的表数据量基本不大,而且后面做的是修改操作,则同步操作使用cachedThreadPool比较好。

6.2 如果是IO密集型的需求,cpu空闲时间会很长,怎么设计线程池比较好?

因为这是一个IO密集型的需求,一个任务的IO时间很长,也就是说在网络IO期间会被阻塞,cpu空闲时间会很长,

对于IO密集型的任务,由于线程在等待IO(例如,磁盘操作或网络请求)时不会消耗CPU,可以使用较多的线程来提高系统的吞吐量。但是,线程本身不是免费的,因为它们需要内存(例如,线程栈)并增加了线程调度的开销。因此,线程数量也不能过多。

以下是针对IO密集型需求设计线程池的一些建议:

  1. 线程数设置

    • 可以考虑将线程数设置得相对较大,常见的策略是CPU核数 * 2 或者更高。
    • 可以考虑根据系统的实际IO等待时间和CPU时间的比例来动态调整线程数。
  2. 选择合适的队列

    • 使用一个有界队列,例如ArrayBlockingQueue,可以防止任务过多积压,导致内存溢出。
    • 根据实际的业务负载,合理选择队列大小。队列太大可能会消耗更多的内存,太小则可能导致过多的任务被拒绝。
  3. 线程的存活时间

    • 对于IO密集型的任务,线程的存活时间可以设置得相对较短,这样可以更快地释放不再需要的线程资源。
  4. 拒绝策略

    • 当队列满且线程数达到最大值时,需要一个策略来处理新进来的任务。默认的策略是抛出RejectedExecutionException,但也可以选择其他策略,例如调用者运行策略,让提交任务的线程自己去执行这个任务。
  5. 使用任务优先级

    • 如果有些IO任务比其他任务更为紧急,可以考虑使用优先级队列。
  6. 监控与调优

    • 监控线程池的关键指标,如:队列长度、线程数、CPU使用率等。这些信息可以用来判断是否需要调整线程池的配置。
    • 根据业务高峰期调整线程池的配置,例如,夜间IO需求较低时,可以减少线程数。

总之,对于IO密集型的需求,主要的目标是最大化系统的吞吐量。这通常意味着需要使用较多的线程,并合理配置线程池的各项参数。

6.3 你觉得线程池的线程是在什么时机被创建进去的

答:核心线程可以在线程池被声明出来时就被创建,这样的话相当于是线程预热的作用,任务一来了就能用,这是通过preStart参数控制的

6.4 你觉得一个线程的状态有哪些呢

答:运行、就绪、阻塞、终止

Java中,一个线程的生命周期包括多个状态。下面是Java线程的主要状态:

  1. 新建(NEW)

    • 当我们创建一个线程对象,但还没有调用它的 start() 方法时,线程处于此状态。
  2. 可运行(RUNNABLE)

    • 线程对象已经调用了 start() 方法,但线程调度器还未选择它作为当前执行的线程时,它处于可运行状态。这也包括了线程正在Java虚拟机内部运行的状态。
  3. 阻塞(BLOCKED)

    • 线程正在等待监视器锁(比如,当一个线程调用了一个同步方法或同步块并且当前同步方法或同步块已被另一个线程所持有,那么这个线程进入BLOCKED状态)。
  4. 等待(WAITING)

    • 线程因调用了以下方法之一而处于等待状态:
      • Object.wait()
      • Thread.join()
      • LockSupport.park()
    • 这是一个不限时的等待,线程需要被其他线程显式地唤醒。
  5. 超时等待(TIMED_WAITING)

    • 线程调用以下方法之一并指定了超时参数,那么线程会处于这个状态:
      • Thread.sleep(long millis)
      • Object.wait(long timeout)
      • Thread.join(long millis)
      • LockSupport.parkNanos()LockSupport.parkUntil()
    • 在超时时间达到或其他线程唤醒它之前,线程会一直处于此状态。
  6. 终止(TERMINATED)

    • 当线程的 run() 方法完成或线程被中断时,线程处于此状态。

在Java编程中,你可以使用 Thread.getState() 方法来获取一个线程的当前状态。这对于调试和线程管理是非常有用的。

6.5 如果发现cpu的使用率非常高,你会重点关注什么状态的线程呢?

答:如果说线程是阻塞态,它应该不怎么占用cpu,被挂起到了阻塞队列中,所以应该重点关注运行态的线程

6.6 如果我发现某一个应用的线程数一直在持续缓慢的增长,然后这个时候访问量也没有太大的波动,而且你的cpu和负载也没有太大的波动,你会怎么分析,通过什么样的方式分析呢,你会关注什么状态的线程?(我答的不对)

答:

答:当一个应用的线程数持续缓慢增长,但访问量、CPU和负载都相对稳定时,可能的情况有:

  • 线程泄露有些线程可能没有被正确关闭,导致随着时间的推移,线程数量逐渐增加。这在使用自定义线程或线程池时可能会发生。

  • 第三方库/组件:可能使用的某个库或组件在内部创建了线程,并且没有正确地管理它们。

为了进一步分析:

  1. 线程堆栈分析:可以使用Java的内置工具 jstack 来查看应用的线程堆栈。这将为每个线程提供一个快照,显示线程在做什么。通过这个工具,可以识别出不断增长的线程是做什么的,是由哪部分代码启动的。

  2. 关注状态:尤其是 WAITINGTIMED_WAITING 的线程。虽然这些线程可能不会消耗大量的CPU,但它们可能是因为等待某个资源或某个条件而被阻塞,导致线程数增长。

  3. 监视工具:使用如VisualVM, JProfiler等工具可以实时监控线程的创建、状态和销毁。这可以帮助识别线程创建的模式和可能的线程泄露。

  4. 日志审查:查看应用的日志,检查是否有异常、错误或其他相关信息,这可能与线程的行为有关。

  5. 代码审查:检查代码中所有创建线程或线程池的地方,确保线程在完成其任务后被正确关闭。

结论:当线程数持续增长但其他指标相对稳定时,很可能是线程管理问题或线程泄露。关键是找到哪些线程不断被创建并为什么它们没有被正确地关闭。

6.7 你是怎么理解火焰图的横向和纵向,能解决我刚刚说的问题嘛

答:横向表示一个线程执行时花费的cpu时间,纵向表示方法的调用栈,因为你刚刚说的是线程不怎么占用cpu,即任务可能执行完了,但是没有被正确释放,所以火焰图是不适用于解决这个问题的

猜你喜欢

转载自blog.csdn.net/yxg520s/article/details/132847187
今日推荐