10年架构师经验带你详细解析四种线程池


10年架构师经验带你详细解析四种线程池

文章插图
 
 线程池介绍:使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池 。
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务 。线程池线程都是后台线程 。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中 。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙 。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值 。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动 。
1.首先我们先看一下获取四种线程池的代码: ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10); ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();可以发现这四种线程池都是由Executors类生成的 。依次点开四个方法的内部实现发现,它们最终调用的都是同一个ThreadPoolExecutor()的构造器,而区别在于构造器的参数不同 。我们来看下ThreadPoolExecutor的参数列表:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {正是由于这几个参数的不同导致了四种线程池的工作机制不同 。参考源码对于参数的注释,我们列出参数的含义 。
  • corePoolSize:核心线程数量,常驻在线程池中的线程,即使它们是空闲的,也不会销毁,除非设置allowCoreThreadTimeOut的值 。
  • maximumPoolSize:线程池最大线程数量
  • keepAliveTime:超过核心数量的额外线程也就是非核心线程,在空闲指定的最大时间后被销毁 。(假设时间为5s,核心线程数为2,当前线程为4,则超过核心线程数的其余两个线程在空闲5秒后会被销毁 。)
  • unit:时间单位
  • workQueue:等待队列
  • threadFactory:生成线程的工厂
  • handler:当等待队列容量满以及线程池数量达到最大时,如何处理新的任务 。
AbortPolicy(默认):直接抛出异常
CallerRunsPolicy:交给调用者所在线程执行 。(假设当前调用者线程是Main,那么就交给Main处理)
DiscardOldestPolicy:丢弃最久未处理的任务,再执行当前任务 。(最久未处理的,在队列中其实就是队列头节点,查看源码的确调用是poll()方法)
DiscardPolicy:丢掉该任务,并且不抛异常 。
线程池的工作机制:
当持续往线程池添加任务,
当前线程数量小于核心线程数量的时候,新增线程 。
当前线程数量达到核心线程数量的时候,将任务放入等待队列 。
当等待队列满的时候,继续创建新线程 。
当线程池数量达到最大并且等待队列也满的时候,采取拒绝服务策略 。
2.接下来我们就根据参数来分析不同的线程池:FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }我们可以看到corePoolSize核心线程数量和maximumPoolSize最大线程数量是一致的,并且keepAliveTime为0 。workQueue是LinkedBlockingQueue,这是一个链表阻塞队列 。可以得出结论:该线程池是一个固定数量的线程池,并且有一个无界的等待队列 。我们可以推导出该线程池适合处理任务量平稳的场景 。例如平均一秒接收10个任务,接收任务量曲线不会很陡峭 。
适合场景:适合少量的大任务(大任务处理慢,如果线程数量多的话,反而在切换线程上下文时损耗,所以控制线程在一定的数量) 。
CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }我们可以看到corePoolSize核心线程池为0,代表该线程没有核心线程池,意味着线程都是可被回收销毁的,线程池中有时会是空的 。并且maximumPoolSize是int最大值,相当于代表该线程池可以无限创建线程 。keepAliveTime为60,代表空闲60秒回收线程 。workQueue是SynchronousQueue,该同步队列是一个没有容量队列,即一个任务到来后,要等待线程来消费,才能再继续添加任务 。我们推导出该线程池适合处理平时没什么任务量,但有时任务量瞬间剧增的场景 。


推荐阅读