Bootstrap

Java 线程池详解

什么是线程池?

线程池是一种管理一系列线程的资源池。当有任务需要处理时,线程池会直接从池中获取可用线程来执行任务。任务处理完后,线程不会被销毁,而是返回线程池,等待下一个任务的到来。

线程池的好处

  • 减小资源消耗:通过重用线程,减少频繁创建和销毁线程的开销。
  • 提高响应速度:任务提交后,无需等待线程创建,可以立即执行。
  • 可管理性:实现对线程的统一管理,便于监控和调试。

如何创建线程池

1. 使用 ThreadPoolExecutor

推荐使用 ThreadPoolExecutor 构造函数来创建线程池。

2. 通过 Executors 工具类

虽然可以通过 Executors 类创建线程池,但不推荐使用默认配置,因为默认创建的线程池使用的是有界队列,可能导致请求堆积,从而引发 OOM(内存溢出)。

ThreadPoolExecutor 的构造函数参数

ThreadPoolExecutor 的构造函数有七个主要参数:

  • corePoolSize:核心线程数,任务队列未达到容量时,最大可以同时运行的线程数量。
  • maximumPoolSize:最大线程数,当任务队列已满时,可以同时运行的最大线程数量。
  • workQueue:任务队列,当线程数达到核心数时,新的任务会被存放在此队列中。
  • keepAliveTime:当线程池中的线程数量大于核心线程数时,非核心线程空闲后等待的时间,超过此时间会被回收。
  • unit:keepAliveTime 的时间单位。
  • threadFactory:用于创建新线程的工厂。
  • handler:拒绝策略。

拒绝策略

当线程池无法接收新任务时,会根据以下拒绝策略进行处理:

  1. AbortPolicy:抛出 RejectedExecutionException,拒绝新任务。
  2. CallerRunsPolicy:在调用线程中运行被拒绝的任务,可能导致主线程阻塞。
  3. DiscardPolicy:直接丢弃新任务。
  4. DiscardOldestPolicy:丢弃最早的未处理任务。

提交任务的方式

  • execute():提交任务时,如果未捕获异常,线程会终止并创建新线程替代。
  • submit():提交任务时,异常被封装在 Future 对象中,线程继续复用。submit() 提供更灵活的错误处理机制。

如何命名线程池

  1. 直接实现 ThreadFactory 接口。
  2. 使用 ThreadFactoryBuilder 的 setNameFormat 方法。

如何设定线程池的大小

  • CPU 密集型:使用 CPU 核心数 + 1
  • IO 密集型:使用 2N,即两倍的 CPU 核心数。

按优先级设计线程池

使用 PriorityBlockingQueue 作为任务队列,可以通过以下方式实现优先级:

  1. 任务实现 Comparable 接口,重写 compareTo 方法。
  2. 创建 PriorityBlockingQueue 时传入 Comparator 对象。

问题及解决方案

  1. OOM 问题:继承 PriorityBlockingQueue,重写 offer 方法,当元素达到一定数量时返回 false
  2. 饥饿问题:设置等待时间,超时后移除并重新加入队列。
  3. 性能问题:使用可重入锁(ReentrantLock),但可能影响性能。

Future 类

Future 类是异步编程的典型应用。它允许我们在任务执行过程中获取结果,提供了更灵活的错误处理机制。

总结

Java 的线程池是一种高效的并发处理机制,通过合理的配置和管理,可以显著提高程序的性能和响应速度。理解线程池的构造、参数、拒绝策略及任务提交方式,对于开发高效的并发应用至关重要。

;