zoukankan      html  css  js  c++  java
  • 线程池梳理

    线程池

    目的
    • 降低资源消耗
      • 通过重复利用已创建的线程降低线程的创建和销毁的消耗
    • 提高利用率
      • 当任务到达时,可以省去线程的创建时间,直接去执行。
    • 方便管理
      • 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以统一分配,调优和监控。
    线程池的四种创建方式
    1. newCachedThreadPool
    • 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    public class TestTwo {
        public static void main(String[] args){
            ExecutorService executorService= Executors.newCachedThreadPool();
    
            for (int i=0; i<20;i++){
                final int temp=i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+","+temp);
                    }
                });
            }
        }
    }
    
    
    1. newFixedThreadPool
    • 可控制线程最大并发数,超出的线程会在队列中等待。
    public class TestTwo {
        public static void main(String[] args){
            ExecutorService executorService= Executors.newFixedThreadPool(3);
    
            for (int i=0; i<20;i++){
                final int temp=i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+","+temp);
                    }
                });
            }
        }
    }
    
    1. newSingleThreadExecutor
    • 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    1. newScheduledThreadPool
    • 创建一个定长线程池,支持定时及周期性任务执行。
    线程池的工作原理

    当一个任务提交至线程池之后,

    1. 线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作线程来执行任务。否则进入2.
    2. 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入3.
    3. 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。如果线程池满了,则交给饱和策略来处理任务。
    • enter image description here
    • enter image description here
    ThreadPoolExecutor

    当一个任务提交至线程池之后,

    1. 线程池首先当前运行的线程数量是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。如果都在执行任务,则进入2.
    2. 判断BlockingQueue是否已经满了,倘若还没有满,则将线程放入BlockingQueue。否则进入3.
    3. 如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。
    ScheduleThreadPoolExecutor
    • ScheduleThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后执行任务,或者定期执行任务。功能与Timer类似,但比Timer更强大,Timer对应的是单个后台线程,而ScheduleThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。
    • enter image description here
    线程池饱和策略

    当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:
    1、AbortPolicy:直接抛出异常
    2、CallerRunsPolicy:只用调用所在的线程运行任务
    3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    4、DiscardPolicy:不处理,丢弃掉。

    • enter image description here
    关闭线程池
    • 原理
      • 遍历线程池中的所有线程,然后逐个调用线程的interrupt方法来中断线程.
    • 关闭方式
      • shutdown
        将线程池里的线程状态设置成SHUTDOWN状态, 然后中断所有没有正在执行任务的线程.
      • shutdownNow
        将线程池里的线程状态设置成STOP状态, 然后停止所有正在执行或暂停任务的线程.
    线程池容量的动态调整

    ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
    setCorePoolSize:设置核心池大小
    setMaximumPoolSize:设置线程池最大能创建的线程数目大小
    当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

    线程池有5种状态
    • 线程池的5种状态是:Running, SHUTDOWN, STOP, TIDYING, TERMINATED。
    • enter image description here
      1. RUNNING

    (01) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
    (02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态!
    道理很简单,在ctl的初始化代码中(如下),就将它初始化为RUNNING状态,并且"任务数量"初始化为0。

    • 2.SHUTDOWN

    (01) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
    (02) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

    • 3.STOP

    (01) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
    (02) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

    • 4.TIDYING

    (01) 状态说明:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
    (02) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
    当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

    • 5.TERMINATED

    (01) 状态说明:线程池彻底终止,就变成TERMINATED状态。
    (02) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

    配置线程池需要考虑哪些因素

    从任务的优先级,任务的执行时间长短,任务的性质(CPU密集/ IO密集),任务的依赖关系这四个角度来分析。并且近可能地使用有界的工作队列。

    性质不同的任务可用使用不同规模的线程池分开处理:

    • CPU密集型:尽可能少的线程,Ncpu+1

    • IO密集型:尽可能多的线程, Ncpu*2,比如数据库连接池

    • 混合型:CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。

    • 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密集型任务设置线程数基本吻合。
    参考
    • 《Java并发编程艺术》
  • 相关阅读:
    LeetCode
    LeetCode
    Django ORM 查询
    The Usage of Pymongo
    MongoDB基操
    Django内置auth模块中login_required装饰器用于类视图的优雅方式
    Django Session配置
    Python虚拟环境
    遇见Flask-Script
    Git使用手册
  • 原文地址:https://www.cnblogs.com/frankltf/p/10317041.html
Copyright © 2011-2022 走看看