学习笔记:线程池~

序言

今天呢来介绍一下线程池,池化技术相信大家已经不陌生了,它产生的主要目的就是为了提高程序的响应速度,以及对资源的复用。

初识线程池

在我们的juc包下有一个类Executors,通过这个类可以很快速的创建出线程池

  • Executors.newFixedThreadPool(n):创建一个带有n个线程的线程池
  • Executors.newSingleThreadExecutor();创建一个只有一个线程
  • Executors.newCachedThreadPool();创建一个线程个数为Integer.MAX_VALUE且带有缓存的线程池

不过我们在平时使用中不要通过这种方法来创建线程池。

使用CachedThreadPool的话它会接受无限多的任务并会创建对应数量的线程最终导致OOM。

使用Fixed/Single的话会造成大量的请求堆积最终导致OOM。

我们可以通过new ThreadPoolExecutor的方式来创建线程池。

线程池的七大参数

在使用new ThreadPoolExecutor创建线程池的时候需要传入7个参数。

  • corePoolSize:用于指定线程池的核心线程数量。
  • maximumPoolSize:用于指定线程池中线程的总数量,总数量-核心线程数量=非核心线程数量。
  • keepAliveTime:指定非核心线程的空闲时间,当空闲时间超过指定时间会将非核心线程回收掉。
  • unit:指定空闲时间的单位。
  • workQueue:指定线程池所使用的阻塞队列,用于存储未来得及处理的任务。
  • threadFactory:线程池所使用的线程工厂,使用默认的即可。
  • handler:用于指定拒绝策略。

核心线程的设置:判断程序是io密集型还是cpu密集型,如果是io密集型则建议设置为机器的核数*2,如果是cpu密集型则建议设置为机器的核数+1。

(获取核数的方法:Runtime.getRuntime().availableProcessors())

线程池的拒绝策略:

①:直接拒绝
②:抛出异常
③:将任务返回给任务的发起方去处理
④:替换掉阻塞队列中存活时间最久的任务

线程池的五种状态

  • Running:运行状态,可正常接收任务,处理任务。
  • ShutDowm:关闭状态,停止接收新的任务,但会处理掉正在运行和队列中的任务。
  • Stop:停止状态,停止接收新的任务,并停止执行当前任务和队列中的任务。
  • Tidying:所有的任务已终止,ctl记录的”任务数量”为0。
  • Terminated:结束状态,线程池彻底结束。

线程池的执行流程

有任务到达线程池,判断是否有核心线程正处于空闲状态,如果有的话核心线程来处理该任务。如果没有空闲的核心线程,则会判断阻塞队列是否满了,没有满的话就将任务存放到阻塞队列当中,如果满了,则会创建非核心线程来处理该任务,如果非核心线程数也达到了指定的阈值,这时就会触发拒绝策略来拒绝掉这次的任务。

经过指定的空闲时间后,线程池又会回收掉创建的非核心线程。

submit和execute方法的区别

submit方法底层调用的就是execute方法,submit是有返回值的,execute方法没有返回值,submit方法会把Runnable和Callable都包装成FatureTask对象。只不过传入的Runnable接口的话最终调用get()方法获取返回值获取的是null值。

线程池的使用

当我们多个业务都要使用线程池的话,最好为每一个业务都创建一个线程池。

业务场景:

现在我们有两个业务,一个是扣费业务,还有一个同步业务,扣费业务中会调用到同步业务,他俩共用同一线程池。

现在线程池中的核心线程数为1,队列中还能存储一个任务。此时有个请求过来了,我们需要调用扣费业务,
于是线程池用核心线程来处理扣费业务,扣费业务需要调用同步业务,此时线程池中已经没有空闲线程来处理同步业务了,只能将同步业务放入阻塞队列中,这时就发生了死锁。

扣费业务要想执行完,必须先执行完同步业务,可此时同步业务在阻塞队列中无法被执行。

阻塞队列中的同步业务要想被执行,必须等待空闲线程的出现,而扣费任务要想执行完释放线程,必要条件就是同步业务执行完,这就产生了死锁问题。

  • 解决方法:

增加一个新的业务线程池,用来隔离父子任务,现有的线程池只用来处理扣费任务,新的线程池用来处理同步任务。这样就可以彻底避免死锁的情况了。

结束语

笔者能力有限,如果有什么错误或者遗漏的地方还请各位读者留言指出O(∩_∩)O~

恭喜滔博让2追3~哈哈

你可能感兴趣的:(学习笔记,java,多线程)