深入剖析线程池一

线程池是什么

​ 刚开始的时候,没有线程池的概念,单线程慢慢满足不了我们的系统开发需求,我们就想多线程开发,但是新来一个任务就创建一个线程,这样肯定不行的,因为线程的创建需要有一定的资源,所以造一个东西能够线程复用,这就是线程池

线程池解决了什么问题
  • 线程复用,降低创建资源的消耗
  • 提高响应速度,来一个任务不用等待线程池的创建,直接从池子里面去拿
  • 线程达到可管理,线程池封装了很多函数可以获取到活跃线程数,阻塞队列任务数等待,可以对线程池进行分配和调控以及可视化
线程池的基本概念
状态
  • Running:运行状态,能够接受任务添加到阻塞队列中并且能够处理任务队列中的任务
  • ShutDown:关闭状态,不接受新的任务,但是会处理阻塞队列中的任务,程序中调用调用代码shutDown()就是这个状态
  • Stop:停止状态,不接受新的任务,也不会处理等待队列中的任务并且会中断正在执行的任务。程序中调用shutDownNown()会对应这个状态
  • Tidyng:所有任务都已终止,有效线程数为0,线程转向此状态会运行terminated()钩子函数
  • Terminated:结束状态,程序调用完terminated()后变成此状态

gaitubao_线程池的五种状态

//表示线程池的状态以及数量   状态初始化为Running状态,运行线程数初始话为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//状态为以下五种
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

知识点

​ q:线程池状态为用一个AtomicInteger的ctl字段表示两个变量的含义,为什么这样干

​ A:一个字段表示两个含义,这样可以避免占用锁资源,我们在下面阅读源码的时候可以看到,线程池在进行一系列骚操作去逻辑判断的时候,经常需要判断线程池的状态已经当前线程数这两个变量值去做逻辑操作,源码用一个变量存储两个变量含义,可以避免在相关决策的时候,出现不一致情况,在修改两个变量含义的时候,不需要占用锁资源去维护,线程池还非常贴心的封装两个函数runStateOf()获取线程池当前运行状态和workerCountOf()获取当前线程数这两个函数或者这两个变量的值。

分类

JDK源码中给我们封装好了几个创建好的线程池,我们一起来了解一下。

  • Executors.newFixedThreadPool(int i)

    •       public static ExecutorService newFixedThreadPool(int nThreads) {
              return new ThreadPoolExecutor(nThreads, nThreads,
                                            0L, TimeUnit.MILLISECONDS,
                                            new LinkedBlockingQueue<Runnable>());
          }
        
      
    • 创建一个拥有 i 个线程的线程池,创建一个定长线程池,可控制线程数最大并发数,超出的线程会在阻塞队列中等待

  • Executors.newSingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>()));
    }
    
    • 创建一个线程的单线程池,只有一个线程在执行,其他都在排队
  • Executors.newCacheThreadPool()

       public static ExecutorService newCachedThreadPool() {
              return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                            60L, TimeUnit.SECONDS,
                                            new SynchronousQueue<Runnable>());
          }
    
    • 创建一个可缓存线程池,如果线程长度超过处理需要,可灵活回收空闲线程,没有可回收的就,则新建新线程。
  • Executors.newScheduledThreadPool(int corePoolSize)

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
              return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    
    • 线程池支持定时以及周期性执行任务,最大线程数为整形的最大数的线程池

    通过源码可以知道,其实就是底层都是使用ThreadPoolExecutor传递不同的参数,虽然JDK默认给我们封装创建好线程池,但是因为很多时候很多时候不能满足我们的需求,阿里开发文档也建议我们自己创建线程池,一起跟着程序员fly公众号研究一下ThreadPoolExecutor源码吧

  public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数含义
  • corePoolSize:核心线程池的数量
  • maximumPoolSize 最大线程池的数量
  • keepAliveTime和unit:线程存货的时间
  • workQueue:阻塞队列
  • threadFactory:线程工厂创建线程
  • handler:拒绝策略
    • AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
    • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案
    • CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
    • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
执行流程
gaitubao_线程池运行流程@2x
设计思想
  • 比如我们住的周围新开了一家海底捞,刚开始人主动并不多,店内正式员工还比较清闲,来了一个顾客,一个服务员能够立马去处理接待,随着后续人来的越来越多,店内人不够了,人再来,我们只能在门店外面拿着号喝着免费的饮料等着,可能就晚间时刻人最多,这个时候外面也一直排着,正式员工也忙不过来, 老板可能会顾一些临时工比如学生这个时候六点到十点来做兼职。这样即可避免打正式员工的钱,又能处理业务。但是可能这家服务太好了,顾客都排到凌晨了 。 再来顾客也招待不了了,这个时候老板只能忍痛拒绝,告诉顾客明天再来。例子肯定不是很准确,但是对于理解线程池的运行流程还是有点帮助的。
    • gaitubao_线程池 (1)
例子
代码一

定义一个线程池,通过代码观察一下现象

   public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 5L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1));
        for (int i = 0; i <4; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程池名称"+Thread.currentThread().getName() + "线程池状态," + threadPoolExecutor.toString());
                }
            });
        }
    }

运行结果

gaitubao_4个线程@2x

结果分析

​ 我们自定义了一个线程池,通过for循环4次打印线程信息,线程池条件 最大线程的数量为4,阻塞队列为1,核心线程池为3,

​ 结果显示active thread最大为3,表明没有创建非核心线程,4个任务,三个核心线程处理,一个放入队列中

代码二
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 5L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1));
        for (int i = 0; i <5; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程池名称"+Thread.currentThread().getName() + "线程池状态," + threadPoolExecutor.toString());
                }
            });
        }
    }

运行结果

gaitubao_5

结果分析

​ 我们自定义了一个线程池,条件是 通过5个for循环打印线程信息,最大线程的数量为4,阻塞队列为1,核心线程池为3,

​ 结果可以看到active thread=4过,创建额外线程1个,5个任务,3个核心线程处理,1个阻塞队列中,1个非核心线程处理,总线程数为3+1=4,没超过最大

代码三
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 5L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1));
        for (int i = 0; i <6; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程池名称"+Thread.currentThread().getName() + "线程池状态," + threadPoolExecutor.toString());
                }
            });
        }
    }

运行结果

gaitubao_6个

结果分析

​ 我们自定义了一个线程池,条件是 通过6个for循环打印线程信息,最大线程的数量为4,阻塞队列为1,核心线程池为3,

​ 结果可以看到active thread=4过,说明创建额外的线程完成任务了,3个核心线程 1个队列,1个非核心线程处理,还剩1个抛出异常

巨人肩膀

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

https://www.cnblogs.com/jajian/p/11442929.html

https://blog.csdn.net/mu_wind/article/details/113806680

https://blog.csdn.net/c10WTiybQ1Ye3/article/details/109684791

闲谈

后面一章我们会读一下ThreadPoolExecutor源码看为啥是这个流程。小伙伴们感觉有用还请麻烦关注一下公众号,个人努力学习中,期待一起成长。

欢迎关注

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值