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

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

posted @ 2022-05-22 09:10  锐洋智能  阅读(520)  评论(0编辑  收藏  举报