Java线程池简单总结

由于最近找工作,面试中被问到这个问题,所以来简单总结一下。
说到线程池,其实就是一种池化技术,跟连接池,内存池,对象池的概念基本上都差不多,所以其实很多东西都是相通的,学会一种其他的东西也是很好理解的。
线程池的优势:

  1. 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  2. 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
  3. 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。

其实总结的来说就是:
降低资源的消耗;提高响应的速度;方便管理线程;
也就是说线程池的出现,是为了提高系统性能,统一管理线程。

一般来说,有四种线程池,分别是

  1. ExecutorService threadPool = Executors.new SingleThreadExecutor();
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

  2. ExecutorService threadPool = Executors.new FixedThreadPool(线程数);
    创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。

  3. ExecutorService threadPool = Executors.new CachedThreadPool();
    可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务。

  4. ExecutorService threadPool = Executors.new newScheduledThreadPool();
    创建一个定长线程池,支持定时及周期性任务执行

其实以上四种在实际开发中一般都是不推荐使用的,阿里巴巴java规范手册中明确说到,线程池不允许使用Executors去创建,而是通过ThreadPoolExecutors的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
因为用SingleThreadExecutor和FixedThreadPool,它允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,从而导致OOM。
使用CachedThreadPool和newScheduledThreadPool,它允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

OOM:当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收,简单的说就是内存用完了。(小伙伴们可以去自行百度)

其实你点进这四种线程池的源码,可以发现它们都是通过new ThreadPoolExecutor()这个去实现的。

所以一般在实际开发中,我们会通过new ThreadPoolExecutor()自定义线程池。
说起new ThreadPoolExecutor()就不得不说它需要的七大参数。

  • corePoolSize:核心线程池大小;
  • maximumPoolSize:线程池最大大小;
  • keepAliveTime:(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
  • TimeUnit:超时单位
  • BlockingQueue workQueue:阻塞队列
  • ThreadFactory:线程工厂,用来创建线程
  • RejectedExecutionHandler:线程池拒绝策略

线程池执行流程,如下
Java线程池简单总结

线程池四种拒绝策略:

  1. AbortPolicy; 默认,队列满了丢任务抛出异常
  2. DiscardPolicy; 队列满了丢任务不抛出异常
  3. DiscardOldestPolicy; 队列满了,尝试去和最早的竞争,不会抛出异常
  4. CallerRunsPolicy; 如果添加到线程池失败,那么主线程会自己去执行该任务

还有一个问题,maximumPoolSize这个值怎么去设置呢,总不能说像设置多大就设置多大吧。一般有两种角度去考虑,io密集型和cpu密集型(考点哦)
一般面试官一问这个问题,就知道你有没有做过事。
所谓io密集型就是:
若任务是耗时IO型任务(比如说读写数据库,文件,网络等),线程数的公式为:
线程数 = CPU核心数 * (1 + 平均等待时间 / 平均处理时间)

cpu密集型:若任务是CPU密集型任务(比如说加密,计算哈希等),线程数可以设置为CPU核心数的1-2倍左右。
一般我们也可以直接用
Runtime.getRuntime().availableProcessors()获取cpu核数

基本上就是这些东西了,反正面试的话,这些东西一定都要会,虽然不是加分相,但要是不会的话,那肯定就是减分相了,大家一起努力学习吧。。。。