线程池ThreadPoolExecutor——基础分析!

1.线程池的作用

一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时候,每当需要执行异步任务时候是直接 new 一线程进行运行,而线程的创建和销毁是需要开销的。使用线程池时候,线程池里面的线程是可复用的,不会每次执行异步任务时候都重新创建和销毁线程。

另一方面线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等,每个 ThreadPoolExecutor 也保留了一些基本的统计数据,比如当前线程池完成的任务数目等。

2.ThreadPoolExecutor 原理探究

类图如下:

3.线程池状态含义:

  • RUNNING:接受新任务并且处理阻塞队列里的任务;

  • SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务;

  • STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务;

  • TIDYING:所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用 terminated 方法;

  • TERMINATED:终止状态,terminated方法调用完成以后的状态。

 线程池状态转换:

       1.RUNNING -> SHUTDOWN:显式调用 shutdown() 方法,或者隐式调用了 finalize(),它里面调用了 shutdown() 方法。

       2.RUNNING or SHUTDOWN -> STOP:显式调用 shutdownNow() 方法时候。

       3.SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候。

       4.STOP -> TIDYING:当线程池为空的时候。

       5.TIDYING -> TERMINATED:当 terminated() hook 方法执行完成时候。

 

4.线程池参数:

  • corePoolSize:线程池核心线程个数;

  • workQueue:用于保存等待执行的任务的阻塞队列;比如基于数组的有界 ArrayBlockingQueue,基于链表的无界 LinkedBlockingQueue,最多只有一个元素的同步队列 SynchronousQueue,优先级队列 PriorityBlockingQueue 等。

  • maximunPoolSize:线程池最大线程数量。

  • ThreadFactory:创建线程的工厂。

  • RejectedExecutionHandler:饱和策略,当队列满了并且线程个数达到 maximunPoolSize 后采取的策略,比如 AbortPolicy (抛出异常),CallerRunsPolicy(使用调用者所在线程来运行任务),DiscardOldestPolicy(调用 poll 丢弃一个任务,执行当前任务),DiscardPolicy(默默丢弃,不抛出异常)。

  • keeyAliveTime:存活时间。如果当前线程池中的线程数量比核心线程数量要多,并且是闲置状态的话,这些闲置的线程能存活的最大时间。

  • TimeUnit,存活时间的时间单位。

5.线程池类型:

1.newFixedThreadPool:创建一个核心线程个数和最大线程个数都为 nThreads 的线程池,并且阻塞队列长度为Integer.MAX_VALUEkeeyAliveTime=0 说明只要线程个数比核心线程个数多并且当前空闲则回收。代码如下:

  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

  public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

2.newSingleThreadExecutor:创建一个核心线程个数和最大线程个数都为1的线程池,并且阻塞队列长为 Integer.MAX_VALUEkeeyAliveTime=0 说明只要线程个数比核心线程个数多并且当前空闲则回收。代码如下:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.newCachedThreadPool:创建一个按需创建线程的线程池,初始线程个数为 0,最多线程个数为 Integer.MAX_VALUE,并且阻塞队列为同步队列,keeyAliveTime=60 说明只要当前线程 60s 内空闲则回收。这个特殊在于加入到同步队列的任务会被马上被执行,同步队列里面最多只有一个任务。代码如下:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

4.ScheduledExecutorService可以用来做定时任务的线程池

6.线程池拒绝策略

1.AbortPolicy,拒绝并抛出异常

public static class AbortPolicy implements RejectedExecutionHandler {
       
        public AbortPolicy() { }
 
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

2.DiscardPolicy,拒绝但不抛出异常

 public static class DiscardPolicy implements RejectedExecutionHandler {

        public DiscardPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

3.DiscardOldestPolicy,执行任务,放弃最老的一个任务

public static class DiscardOldestPolicy implements RejectedExecutionHandler {

        public DiscardOldestPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

4.CallerRunsPolicy 直接执行任务

public static class CallerRunsPolicy implements RejectedExecutionHandler {
        
        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

7合理配置线程池

 

CPU密集

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些。

IO密集

IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

 

接着上一篇探讨线程池留下的尾巴,如何合理的设置线程池大小。

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

1.  任务的性质:CPU密集型任务、IO密集型任务、混合型任务。

2.  任务的优先级:高、中、低。

3.  任务的执行时间:长、中、短。

4.  任务的依赖性:是否依赖其他系统资源,如数据库连接等。

性质不同的任务可以交给不同规模的线程池执行。

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

若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。

当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。

 

最佳线程数目 = ((线程等待时间+线程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密集型任务设置线程数基本吻合。

CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

操作系统之名称解释:

某些进程花费了绝大多数时间在计算上,而其他则在等待I/O上花费了大多是时间,

前者称为计算密集型(CPU密集型)computer-bound,后者称为I/O密集型,I/O-bound。

欢迎观看我下一篇,线程池ThreadPoolExecutor源码深度解析!https://blog.csdn.net/zhangkaixuan456/article/details/106841419

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章