内容:    1、什么是线程池

            2、线程池的优点

           3、线程池原理(重点)

           4、理解线程池(重点)

           5、如何设置线程池

 

1、什么是线程池

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

  线程的状态:NEW  RUNNABLE  RUNNING  BLOCKED  DEAD

2、线程池的优点

(1)、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。

(2)、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

 

3、线程池的实现原理

 

例子: 核心线程数:2

           任务队列大小:3

           最大线程数:5

(1)首次运行线程池:

         同时8个请求: 第1请求和第2请求,  未达到 核心线程数, 直接执行。

                                 第3,4,5个请求,已达到核心线程数, 未达到任务队列大小,直接 进 任务队列。

                                第6,7,8个请求,任务队列也已经满了,未达到最大线程数(还有 3个线程,也就是5减2等于3),则新开线程。

                       所以 所有的请求都会成功处理。

       假如设置线程的存活时间为1分钟,10分钟后,线程池里面只有2个核心线程。

     再次同时8个请求: 第1请求和第2请求  已达到核心线程数,未达到任务队列大小,则进任务队列。

                            第3请求  ,已达到核心线程数,未达到任务队列大小,则进任务队列。

                           第4,5,6请求,任务队列已经满了,未达到最大线程数,则新开线程。 (这种情况是CPU没有及时切换到核心线程的情况)。

                          第7,8请求,只能走拒绝策略了。

                      所以  不一定所有的请求都会成功处理

       但有一种可能会成功处理,那就是入队列的时候,CPU及时切换到核心线程,核心线程立即从队列里面取走。

            比如: 第1请求和第2请求  已达到核心线程数,未达到任务队列大小,则进任务队列。

                       此时CPU切换到核心线程,2个核心线程从队列里面取走了,此时队列为空。

                        第3,4,5个请求,已达到核心线程数, 未达到任务队列大小,直接 进 任务队列。

                       第6,7,8个请求,任务队列也已经满了,未达到最大线程数(还有 3个线程,也就是5减2等于3),则新开线程。

 

4、理解线程池

以java定义的线程池为例:

public ThreadPoolExecutor(
  int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler) ;

corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;

maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;

keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;

unit:keepAliveTime的单位

workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;

              同步阻塞队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,每执行一个插入操作就会阻塞

             有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现

             无界的任务队列:有界任务队列可以使用LinkedBlockingQueue实现

             优先任务队列:优先任务队列通过PriorityBlockingQueue实现

threadFactory:线程工厂,用于创建线程,一般用默认即可;

             自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级

handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;

     AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;

     CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;

    DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;

    DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

 

5、如何设置线程池

要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:

  1. 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
  2. 任务的优先级:高、中、低。
  3. 任务的执行时间:长、中、短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接等。

(1)根据经验值:性质不同的任务可以交给不同规模的线程池执行。

对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理。

若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。
当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。

(2)根据理论值:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

可以得出一个结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。

 

参考:https://www.jianshu.com/p/7726c70cdc40

posted on 2023-07-20 15:46  毛会懂  阅读(5)  评论(0编辑  收藏  举报