JUC学习:线程池

java线程池学习

线程池原理的简单理解

线程池原理和银行办理业务差不多

线程池就像银行,有需求的时候给你安排窗口办理业务,人数多了就去等待队列去等待,如果哪个窗口的人办理完业务了,等待队列里面的人就会选择该窗口进行业务办理,(有空闲窗口就会从等待队列里面选取,不会直接从外面选取)如果等到队列都满了,还有人要办理业务,就会额外开一些窗口来继续办理业务,如果全部窗口都打开了,等待队伍也满了,还有需求需要办理的话,就会执行拒绝策略,拒绝后面的需求,并执行相应的策略

开放的窗口1,2,3相当于核心线程池数量,最大线程池数量就是银行里面所有的窗口数量

情况一

当办理业务的人数<开放窗口数量+等待队列里面的数量,就直接进行业务办理

在这里插入图片描述

情况二

最大窗口数量+等待队列里面的数量>当办理业务的人数>开放窗口数量+等待队列里面的数量,就会打开未开放的窗口,让等待队列里面的人进来办理业务,缓解压力(如果有个窗口长期没有人来办理业务,即保持空闲一段时间就会关闭该窗口,避免浪费资源)

在这里插入图片描述

在这里插入图片描述

情况三

当办理业务的人数>最大窗口数量+等待队列里面的数量,即银行无法承受人员压力,就会执行拒绝策略,拒绝后面这部分的人

在这里插入图片描述

线程池的创建和使用(建议自定义创建)

 ExecutorService executorService = new ThreadPoolExecutor(3, 5, 2L,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
线程池的参数说明
  1. 参数一是:int corePoolSize,核心线程数: 也是线程池中常驻的线程数

  2. 参数二是:int maximumPoolSize,最大线程数:在核心线程数的基础上可能会额外增加一些非核心线程

    需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)

  3. 参数三是:long keepAliveTime,线程存活时间(但有线程长时间没有使用会对这个线程进行销毁):非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程)

  4. 参数四是:TimeUnit unit,keepAliveTime的时间单位

  5. 参数五是:BlockingQueue<Runnable> workQueue,等待队列(要设置队列长度) :可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中

  6. 参数六是:ThreadFactory threadFactory,线程工厂:可以不用填写会默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建

  7. 参数七是:RejectedExecutionHandler handler,拒绝策略:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy

workQueue队列
  • SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
  • LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务处理速度造成请求堆积)可能导致内存占用过多或OOM
  • ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务

handler拒绝策略

  • AbortPolicy:中断抛出异常
  • DiscardPolicy:默默丢弃任务,不进行任何通知
  • DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
  • CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

关闭线程池

  • shutdownNow():立即关闭线程池(暴力),正在执行中的及队列中的任务会被中断,同时该方法会返回被中断的队列中的任务列表
  • shutdown():平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
  • isTerminated():当正在执行的任务及对列中的任务全部都执行(清空)完就会返回true
常见的几种自动创建线程池方式

自动创建线程池的几种方式都封装在Executors工具类中(但建议使用自定义的方式创建):

  • new FixedThreadPool:使用的构造方式为new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()),设置了corePoolSize=maxPoolSize,keepAliveTime=0(此时该参数没作用),无界队列,任务可以无限放入,当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常 没有非核心线程,队列无限长,可以设置核心线程的数量

    在这里插入图片描述

  • new SingleThreadExector:使用的构造方式为new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0),基本同newFixedThreadPool,但是将线程数设置为了1,单线程,弊端和newFixedThreadPool一致 没有非核心线程,队列无限长,核心线程数量为1,既当newFixedThreadPool

    核心线程数量为1的时候就是newSingleThreadExector

    在这里插入图片描述

  • new CachedThreadPool:使用的构造方式为new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()),corePoolSize=0,maxPoolSize为很大的数,同步移交队列,也就是说不维护常驻线程(核心线程),每次来请求直接创建新线程来处理任务,也不使用队列缓冲,会自动回收多余线程,由于将maxPoolSize设置成Integer.MAX_VALUE,当请求很多时就可能创建过多的线程,导致资源耗尽OOM。这里是没有核心线程,无限多的非核心线程,和队列长度为1的队列

    在这里插入图片描述

  • new ScheduledThreadPool:使用的构造方式为new ThreadPoolExecutor(var1, 2147483647, 0L, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()),支持定时周期性执行,注意一下使用的是延迟队列,弊端同newCachedThreadPool一致

//这里默认拒绝策略为AbortPolicy
private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue(10));
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().build();
 
private static ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), threadFactory, new ThreadPoolExecutor.AbortPolicy());
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-d%").build();
Springboot中使用线程池
@Configuration
public class ThreadPoolConfig {
    @Bean(value = "threadPoolInstance")
    public ExecutorService createThreadPoolInstance() {
        //通过guava类库的ThreadFactoryBuilder来实现线程工厂类并设置线程名称
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();
        ExecutorService threadPool = new ThreadPoolExecutor(10, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());
        return threadPool;
    }
}
  //通过name=threadPoolInstance引用线程池实例
  @Resource(name = "threadPoolInstance")
  private ExecutorService executorService;
 
  @Override
  public void spikeConsumer() {
    //TODO
    executorService.execute(new Runnable() {
    @Override
    public void run() {
      //TODO
     }});
  }

设置核心线程数的时候需要注意的点

cpu密集型:

cpu使用率较高(也就是一些复杂运算,逻辑处理),所以核心线程数一般只需要cpu核数的线程就可以了。 这一类型的在开发中多出现的一些业务复杂计算和逻辑处理过程中。

//获取CPU的核数
Runtime.getRuntime().availableProcessors();
I/O密集型:

​ cpu使用率较低,程序中会存在大量I/O操作占据时间,导致线程空余时间出来,所以通常就需要开cpu核数的两倍的线程, 当线程进行I/O操作cpu空暇时启用其他线程继续使用cpu,提高cpu使用率 通过上述可以总结出:线程的最佳数量: 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。这一类型在开发中主要出现在一些读写操作频繁的业务逻辑中。

​ 开发中我们经常会使用到线程池来处理一些业务,而在不新增设备的情况下,我们所能使用的线程资源又不是无线的,那么高并发、任务执行时间短的业务怎样使用线程池?还有并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

接下来我们进行一一分析:

1:高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

2:并发不高、任务执行时间长的业务这就需要区分开看了:

a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务

b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换

(其实从一二可以看出无论并发高不高,对于业务中是否是cpu密集还是I/O密集的判断都是需要的当前前提是你需要优化性能的前提下)

3:并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,我们的项目使用的时redis作为缓存(这类非关系型数据库还是挺好的)。增加服务器是第二步(一般政府项目的首先,因为不用对项目技术做大改动,求一个稳,但前提是资金充足),至于线程池的设置,设置参考 2 。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件(任务时间过长的可以考虑拆分逻辑放入队列等操作)对任务进行拆分和解耦。

线程池底层源码分析

首先要读懂下面这俩副图,底层原理思想就是基于这俩图实现的

在这里插入图片描述
在这里插入图片描述

1.线程复用

复用的过程主要如上图所示

execute->addWorker()->Worker->t.start()->run()–>runWorker()->task.run()->processWorkerExit()->addWorker()

这里是个循环,会不断的调用,直到task为空

2.优先级

线程池存放是有优先级的,先会存放核心–>队列–>非核心

执行优先级: 核心–>非核心–>队列

3.源码理解图

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

我们来看看Worker里面是如何运行的

在这里插入图片描述

Worker线程开启以后会调用run()方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值