一、什么是线程池,为什么需要线程池

池化思想在计算机领域并不少见,在SQL连接,TCP链接中也常常见到,本质思想都是一样的,就是资源复用,线程池也差不多就是这个道理,它将多个线程资源先存储在一个池子中,,当有新的任务出现时可以避免创建/销毁线程所带来的性能开销,只要复用池子中的线程来执行对应的任务即可。
总而言之,就是使用线程池这个工具可以更好地管理线程,避免不必要的开销和内存的占用。

二、线程池的工作原理

线程池只要两个数据结构来配合工作,一个是阻塞队列BlockQueue,另一个就是线程池,它们的关系如图

线程池还包括核心线程池,这个图没有画出(我懒得画了,也没有找到合适的图片)
Question1:线程池添加线程的规则(经典八股文,不过还算是有意义的八股文)

  1. 当前线程数小于核心线程数,则不管工作线程有没有空闲的,都会创建一个新的线程来解决对应的任务
  2. 如果当前线程数大于核心线程数,但小于最大线程数,那么新来的任务加入工作队列中
  3. 如果工作队列满了,但当前线程数还是小于最大线程数,则创建新的线程放入线程池中运行任务
  4. 如果队列满了,并且当前线程数已经超过最大线程数了,那么就拒绝这个任务。

Question2:线程池的关键参数
从上面的提交流程也可以看出几个关键参数,核心线程数corePoolSize,mazPoolSize, workQueue, Handler(拒绝策略),此外我们还可以设置线程存活时间keepAlive,和创建线程的工厂模式(可以指定线程的线程名,线程优先级,线程是否为守护线程)(不过不常用)

Question3:如何创建线程池
阿里巴巴的《java开发手册》中这样写

【强制要求】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

至于如何合理配置相关参数,本人还没参加工作,还不会,以后秃头了再来补充(不知道要几年了)
Question4:为什么不要用Executors创建线程池
虽说这样创建不好,但是在平时学习的时候,使用Executors还是比较方便的,因为它提供了几个接口来创建线程池

  • newFixedThreadPool
  • newSingleThreadExecutor
  • newCacheThreadPool

常用的就是这四个,当然还有其他的,我没有用过,可以参考其他大佬
虽然他们很方便使用,但是可以看看它们的源码就知道他们的弊端了,他们底层依旧是调用ThreadPoolExecutor,只不过默认设置的参数不一样

nexFixedThreadPool最大的问题是它核心线程数和最大线程数一样,那么keepAlive参数就无效了,因为这个参数只对核心线程数以外的线程起作用,因此是0,同时它采用的是LinkedBlockingQueue阻塞队列,这个队列跟链表性质一样,没有长度限制,所以容易OOM

线程池总结-小白菜博客
同理newFixedThreadPool,它使用的工作队列也是LinkedBlockingQueue,所以也容易导致OOM

newCachedThreadPool最大的问题就是它的最大线程数是没有限制的,这样也容易导致创建过多线程而OOM,同时,它使用的工作队列是SynchronousQueue,这个是容量为0(注意有的地方写的是1其实是不对的)的队列,它不持有任何元素,而是直接传递元素

可以看到即使做了这一层包装,但是各有各的问题,因为实际工作中还是不要使用的好。

Question5:线程池中任务如何提交
常见的有两种方法,一种就是执行execute,这个方法适合没有返回值的任务,而submit会返回一个Future对象,通过Future的get方法获取执行结果,另外对于有调度的线程池,也可以使用schedule的方法,问题不大
Question6:如何停止线程池
使用shutdown方法,这个方法不会立刻停止线程池,而是要等待时间把线程池中的任务运行完,这个等待过程中线程池不会再接受新的任务,使用isShutDown就可以判断线程池有没有关闭,使用isTerminated就可以查看里面线程有没有运行完,如果想要立刻结束线程池那么就可以使用shownDownNow,它有个返回值返回未执行完的任务列表
Question6:线程池的拒绝策略有哪些(饱和策略)
有四种,默认的是直接抛出异常,第二种就是舍弃任务但并没有抛出异常,第三种就是抛弃最老的任务,第四种就是执行谁提交的谁运行策略(一般是主线程提交任务,所以交给主线程去运行)