高性能编程-03 线程池的应用与实现原理

目录

一、线程池的相关概念

二、Java中线程池相关接口及实现类

三、线程池的工作原理

四、定时任务线程池ScheduledThreadPoolExecutor常用API

五、创建线程池的API

六、其他注意事项

七、小结

 


       多线程并行处理请求的场景十分常见,如果每次都自己手动创建线程、启动线程、中止线程,业务处理的效率肯定不会很高。线程数量并不是越多越好,因为:1.线程在java中是一个对象,更是操作系统的资源,线程创建、销毁需要时间,如果创建时间+销毁时间>执行任务的时间,就很不划算;2.java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小为1M,这个栈空间是需要从系统内存中分配的,线程过多,会消耗很多的内存;3.操作系统需要频繁切换线程上下文,影响性能。所以聪明的程序员发明了线程池这个东西,线程池的目的就是为了帮你创建线程、管理线程,将大部分的时间用于真正的业务处理上。

一、线程池的相关概念

       线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务。

       工作线程:线程池中的线程,在没有任务时,处于等待状态,可以循环的执行任务。

       任务接口:每个任务必须实现的接口,以供工作线程调度任务时执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。

       任务队列:用于存放没有处理的任务,提供一种缓冲机制。

图3-1 线程池

 

 

 

二、Java中线程池相关接口及实现类

 

表3-1 线程池相关接口及实现类

类型

名称

描述

接口

Executor

最上层的接口,定义了执行任务的方法execute

接口

ExecutorService

继承了Executor,扩展了Callable、Future、关闭方法。

接口

ScheduledExecutorService

继承了ExecutorService,增加了定时任务相关的方法。

实现类

ThreadPoolExecutor

基础、标准的线程池实现

实现类

ScheduledThreadPoolExecutor

继承了ThreadPoolExecutor,实现了ScheduledExecutorService中定时任务相关的方法。

图3-2  线程池相关接口及实现类

 

 

 

三、线程池的工作原理

       通过构造方法ThreadPoolExecutor(int corePoolSize,
                                                              int maximumPoolSize,
                                                              long keepAliveTime,
                                                              TimeUnit unit,
                                                              BlockingQueue<Runnable> workQueue,
                                                              RejectedExecutionHandler handler),创建线程池时,需要注意传递的各参数的含义。

                 corePoolSize核心线程数;

                 maximumPoolSize最大线程数;

                 keepAliveTime非核心线程最大空闲时间,超过空闲时间将被销毁;

                 unit非核心线程最大空闲时间的单位;

                 workQueue任务队列;

                 handler任务被拒绝时的执行器。

       当一个任务交给线程池执行的时候,首先如果有核心线程空闲,则交给核心线程执行,如果所有核心线程都有任务在执行,则放入任务队列中,如果任务队列满了,放不进去了,就会创建非核心线程,让非核心线程执行任务,如果非核心线程+核心线程的数量超过了最大线程数,则会拒绝执行,执行拒绝策略。当创建的非核心线程执行完任务后,进入空闲状态,如果空闲时间超过限定的时间后依然没有任务可执行则会被销毁。流程图如下:

图3-3  线程池的工作原理

 

 

四、定时任务线程池ScheduledThreadPoolExecutor常用API

       构造方法:public ScheduledThreadPoolExecutor(int corePoolSize){...}

       提交任务(定时执行):schedule(Callable<V> callable, long delay, TimeUnit unit)

              提交任务时可以指定多久之后才执行任务;

       周期任务(周期执行):scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

               以固定的周期执行任务,如果任务执行的时间超过设定的周期,则上一个任务执行完后立即执行下一个任务。即当任务执行时间小于设定的周期时,任务执行周期=设定的周期;当任务执行时间大于设定的周期时,任务执行周期=任务执行的时间。

        周期任务(周期执行):scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)

                以固定的时间间隔执行任务,上一个任务执行完后,需要延迟固定间隔后再执行下一个任务。任务执行的周期=任务执行时间+间隔时间。

图3-3 atFixed 和withFixed 的区别

 

 

五、创建线程池的API

       工具类java.util.concurrent.Executors为我们提供了几种常用的线程池创建方法:

              Executors.newFixedThreadPool(int nThread):创建出来的线程池的核心线程数等于最大线程数等于nThread,任务队列为无界队列。即固定几个人在干活,即使来再多的活都可以排队。

              Executors.newCachedThreadPool():创建出来的线程池的核心线程数为0,最大线程数等于Integer.MAX_VALUE,同步队列。即我没有仓库,来一个活我请一个临时工(创建一个线程)。

              Executors.newSingleThreadExecutor():创建出来的线程池的核心线程数等于最大线程数等于1,任务队列为无界队列。即我单干,不管来多少你先排着。

              Executors.newScheduledThreadPool(int corePoolSize)创建出来的线程池的核心线程数为corePoolSize,最大线程数为Integer.MAX_VALUE,任务队列为延时队列DelayedWorkQueue,超出核心线程数量的线程存活时间为0秒。

六、其他注意事项

       1、终止线程池:

              pool.shutdown();不会接收新任务(会执行拒绝逻辑),但原来的任务会被执行完。

              pool.shutdownNow();立即停止,已提交的任务也不会被执行完,已提交正在执行的任务会抛出异常InterruptedException。返回已提交但没被执行的任务集合。

 

       2、线程池如何确定合适的线程数量:

              首选分清业务类型,如果是计算型任务,线程数一般设置为CPU数量的1-2倍;如果是IO型任务,等待时间可能较长,则线程数需要设置大一点,比如tomcat默认的最大线程数为200。生产环境下,观察CPU的使用率,达到80%左右是比较合理的。

七、小结

       本小节主要讲解了线程池的相关概念、工作原理、常用API及合理线程数量的估算方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值