• Java 线程池 ThreadPoolExecutor


    目录

        • 0,Java 线程状态转换
        • 1,Java 线程池的三种创建方式
        • 2,ThreadPoolExecutor 类的原理
          • 1,构造方法及参数含义
          • 2,一些重要方法
          • 3,线程池状态
          • 4,线程池模型
        • 3,任务的执行过程
        • 4,合理设置线程池的大小

    0,Java 线程状态转换

     1,Java 线程池的三种创建方式

        newCacheThreadPool():核心线程数是 0,非核心线程数是 2^31 - 1,没有阻塞队列(不存放任务)
            适合任务数比较密集,但每个任务执行时间较短的情况
        newFixedThreadPool(n):核心线程数是 n,没有非核心线程,阻塞队列最大为 2^31 - 1
            适用于任务量已知,相对耗时的任务
        newSingleThreadExecutor():核心线程数是 1,没有非核心线程,阻塞队列最大为 2^31 - 1
            适用于多个任务排队执行

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(
        		0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    			new SynchronousQueue<Runnable>());
    }
    
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(
        		nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
    			new LinkedBlockingQueue<Runnable>());
    }
    
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    			new LinkedBlockingQueue<Runnable>()));
    }
    

    可以看到以上三种创建方式实际使用的都是 ThreadPoolExecutor 类,只是参数不同而已。

    2,ThreadPoolExecutor 类的原理

        ThreadPoolExecutor:基本的线程池实现
        ScheduledThreadPoolExecutor:任务调度线程池:带有定时任务的线程池
            在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用
            但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务

    线程池有五种状态:

        RUNNING:正常运行状态,可接收新任务,可处理阻塞队列中的任务
        SHUTDOWN:不会接收新任务,但会处理阻塞队列剩余任务
        STOP:会中断正在执行的任务,并抛弃阻塞队列任务
        TIDYING:任务全执行完毕,活动线程为 0,即将进入终结
        TERMINATED:终结状态

    1,构造方法及参数含义

    ThreadPoolExecutor 类位于 java.uitl.concurrent 包中,其参数含义如下:

    public ThreadPoolExecutor(
    		int corePoolSize,					// 核心线程数,核心线程就是一直存在的线程
    		
            int maximumPoolSize,				// 最大线程数,表示线程池中最多能创建多少个线程
            									// 非核心线程数 = 最大线程数 - 核心线程数
            									
            long keepAliveTime,					// 针对非核心线程而言,表示线程没有任务执行时最多保持多久时间会终止
    			// 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
    			// 		当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到 keepAliveTime,
    			// 		则会终止,直到线程池中的线程数不超过corePoolSize
    			// 但是如果调用了 allowCoreThreadTimeOut(boolean) 方法
    			// 		在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用
    			// 		直到线程池中的线程数为 0
            TimeUnit unit,						// 时间单位,与 keepAliveTime 配合使用,针对非核心线程
            
            BlockingQueue<Runnable> workQueue,	// 存放任务的阻塞队列
            
            ThreadFactory threadFactory,		// 创建线程的工厂,可以为线程创建时起个好名字
            
            RejectedExecutionHandler handler	// 拒绝策略
            									// 任务太多的时候会进行拒绝操作
            									// 核心线程,非核心线程,任务队列都放不下时
    )
    

     unit 参数有7种取值,在 TimeUni t类中有7种静态属性:

    TimeUnit.DAYS;              //天
    TimeUnit.HOURS;             //小时
    TimeUnit.MINUTES;           //分钟
    TimeUnit.SECONDS;           //秒
    TimeUnit.MILLISECONDS;      //毫秒
    TimeUnit.MICROSECONDS;      //微妙
    TimeUnit.NANOSECONDS;       //纳秒
    

     workQueue 参数表示一个阻塞队列,用来存储等待执行的任务,有以下几种选择:

        ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
        LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE
        SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务

    handler 参数表示拒绝策略,当线程池的任务缓存队列已满,并且线程池中的线程数目达到 maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。

     handler 通常有以下四种策略:

        ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常(这是默认策略)
        ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但不抛出异常
        ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
        ThreadPoolExecutor.CallerRunsPolicy:由调用线程(调用者)处理该任务

    2,一些重要方法

    ThreadPoolExecutor 类中的几个重要方法:

        execute():向线程池提交一个任务,交由线程池去执行
        submit():也是向线程池提交任务,但是和execute()方法不同,它能够返回任务执行的结果
            它实际上还是调用的 execute() 方法,只不过它利用了 Future 来获取任务执行结果
        invokeAll():提交一个任务集合
        invokeAny(): 提交一个任务集合,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
        shutdown():关闭线程池,再也不会接受新的任务
            不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止
        shutdownNow():关闭线程池,再也不会接受新的任务
            立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
        isShutdown():不在 RUNNING 状态的线程池,此方法就返回 true
        isTerminated():线程池状态是否是 TERMINATED

    动态调整线程池的大小:

        setCorePoolSize:设置 corePoolSize
        setMaximumPoolSize:设置 maximumPoolSize

    还有一些方法:

        getQueue()
        getPoolSize()
        getActiveCount()
        getCompletedTaskCount()

    3,线程池状态

    在 ThreadPoolExecutor 中定义了一个 volatile 变量,另外定义了几个 static final变量表示线程池的各个状态:

    volatile int runState;				// 当前线程池的状态
    static final int RUNNING    = 0;
    static final int SHUTDOWN   = 1;
    static final int STOP       = 2;
    static final int TERMINATED = 3;
    

     线程池的状态变化:

        当创建线程池后,线程池处于 RUNNING 状态
        当调用了 shutdown() 方法,则线程池处于 SHUTDOWN 状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕
        当调用了 shutdownNow() 方法,则线程池处于 STOP 状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务
        当线程池处于 SHUTDOWN 或 STOP 状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为 TERMINATED 状态

    4,线程池模型

     大体上由三大部分组成:

        核心线程:一直存在的线程,数量为 corePoolSize
            在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务;当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中;
            除非手动调用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,来预创建线程,即在没有任务到来之前就创建线程。
        非核心线程:临时创建的线程,会根据任务的多少来进行创建
            当然总的非核心线程的数量不能大于 maximumPoolSize - corePoolSize
            如果总的非核心线程的数量很大,并且任务非常多,就会创建非常多的线程
        任务队列:
            SynchronousQueue:只能存放一个任务
            LinkedBlockingQueue:可以无限存放任务,如果任务超级多,会有内存溢出的可能

        同一时刻,线程池中所能接纳的最大任务数为:maximumPoolSize + 任务队列的长度;
        当超出这个范围后,如果再有新的任务过来,将会被拒绝。

    线程池中的线程的初始化:

        默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程
        在实际中,如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
            prestartCoreThread():初始化一个核心线程
            prestartAllCoreThreads():初始化所有核心线程

    处理任务的流程:

        如果当前线程池中的线程数目小于 corePoolSize,每来一个任务,就会创建一个线程去执行这个任务
        如果当前线程池中的线程数目>=corePoolSize,每来一个任务,会尝试将其添加到任务缓存队列当中
            若添加成功,则该任务会等待空闲线程将其取出去执行
            若添加失败(一般来说是任务缓存队列已满),则会尝试创建临时线程去执行这个任务
        当核心线程,任务队列,非核心线程都使用完后,如果还有新的任务过来,将会进行拒绝处理

        线程复用: 当线程处理完已分配的任务后,在没有销毁之前,还会用于去处理新的任务。这样可以避免创建过多的线程。

    3,任务的执行过程

    源码部分是 ThreadPoolExecutor 类中的 execute 方法:

     流程图如下:

     

     4,合理设置线程池的大小

    一般需要根据任务的类型来配置线程池大小:

        如果是 CPU密集型任务,就需要尽量压榨 CPU,可以设为 CPU个数+1
        如果是 IO密集型任务,可以设置为 CPU个数*2

    这只是一个参考值,具体的设置还需要根据实际情况进行调整,可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

  • 相关阅读:
    Mac下eclipse安装SVN插件
    python中函数参数*args和**kw的区别
    Linux下安装JDK1.8
    SpringBoot Profiles特性
    一张图讲解单机FastDFS图片服务器安装步骤(修订版)
    一张图讲解最少机器搭建FastDFS高可用分布式集群安装说明
    Zookeeper作为配置中心使用说明
    一张图秒懂微服务网络架构
    TestNG的静态方法mock的步骤
    Java中indexOf的用法
  • 原文地址:https://www.cnblogs.com/interdrp/p/16296946.html
Copyright © 2020-2023  润新知