ThreadPoolExecutor线程池使用详解【图文教程】

平凡也就两个字: 懒和惰;
成功也就两个字: 苦和勤;
优秀也就两个字: 你和我。
跟着我从0学习JAVA、spring全家桶和linux运维等知识,带你从懵懂少年走向人生巅峰,迎娶白富美!
关注微信公众号【 IT特靠谱 】,每一篇文章都是心得总结,跟我学习你就是大牛!

ThreadPoolExecutor线程池使用详解【图文教程】

ThreadPoolExecutor源码的学习(基于JDK 1.7)

1 继承关系

      ThreadPoolExecutor就是我们经常说的大名鼎鼎的线程池,Executors工厂创建的线程池都是该类的实例,通过调节参数的大小创建适用于各个场景的线程池。通过源码我们了解到ThreadPoolExecutor继承了AbstractExecutorService,该抽象类为线程池提供了默认实现。

2 ThreadPoolExecutor的构造方法

     ThreadPoolExecutor有很多重载的构造函数,所有构造函数最终都调用了一个构造函数,只是有些构造函数有默认参数而已,看下最终调用的构造函数:

2.1 构造方法参数详解

 上面这个构造函数有很多参数,我们分别来解释每一个参数的意义:

(1)corePoolSize:核心线程数

      核心线程池的大小。当总的创建的线程个数小于CorePoolSize个时,都会创建一个新线程来执行提交的任务请求,核心线程池中的线程创建之后不会销毁(任务执行完之后不会销毁,而是处于空闲状态)。默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true

(2)maximumPoolSize:线程池允许最大的线程数

      线程池所能容纳的最大线程数。表示设置线程池的大小,指定线程池允许的最大线程数量。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。

(3)keepAliveTime:闲置线程最长存活时间

      非核心线程中闲置线程最长可以存活的时间,超过这个时间就会被回收。unit:时间单位。

      1)如果线程池中的线程个数大于corePoolSize核心线程池大小,且多余的线程(非核心线程)处于空闲状态的时间超过了keepAliveTime设置时间,那么多余的线程将会被终止。当线程池没被使用的时候,这种机制可减少资源消耗。

      2)keepAliveTime线程存活时间参数可以被动态改变,通过setKeepAliveTime(long time, TimeUnit unit)方法来动态设置keepAliveTime值。将time设置成Long.MAX_VALUE,且unit设置成NANOSECONDS(纳秒),则表示在线程池关闭(shut down)之前超出核心线程池的且处于空闲状态的线程永远不会终止,永远处于空闲状态!(即:线程永远存活)。

      默认情况下(allowCoreThreadTimeOut=false), keep-alive策略只有当线程池中的线程数超过了核心线程池大小(corePoolSize)的时候才会起作用,且只对超出核心线程池大小的线程数起作用,对核心线程池不起作用(核心线程池中的线程永远不会终止,除非线程池shut down关闭)。但是只要keepAliveTime值不为0,就可以通过               

      allowCoreThreadTimeOut(boolean)方法来将这个超时策略应用到核心线程。(也就是说如何将超时策略用到了核心线程池上之后,当核心线程池中的线程处于空闲状态,且空闲时间超过keepAliveTime设定时间后会被终止掉!)。

(4)unit:指定keepAliveTime的单位

      指定keepAliveTime的单位,如:TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。

(5)workQueue:线程池中的任务队列

      当池中线程数大于corePoolSize时,新来的任务保存到该队列。常用的有三种队列,SynchronousQueueLinkedBlockingDequeArrayBlockingQueue

(6)threadFactory:线程工厂

      线程池中的所有线程都是通过这个工厂创建的。用ThreadFactory来创建线程。如果没指定用哪个ThreadFactory,则会使用defaultThreadFactory默认线程工厂来创建线程。defaultThreadFactory默认线程工厂创建的线程具有相同的优先级(NORM_PRIORITY),非守护进程(non-daemon)状态,并且在相同的线程组(ThreadGroup)里。

      通过指定自定义ThreadFactory线程工厂(实现ThreadFactory接口的自定义类),在线程工厂里可以设置创建的每个线程的名称,线程所在组,优先级和守护进程状态等等。【By supplying a different ThreadFactory, you can alter the thread's name, thread group, priority, daemon status,etc】

(7)handler:任务拒绝执行的策略

    当线程池无法处理新来任务时的处理策略。当Executor执行器shut down关闭 或 工作线程超过maximumPoolSize最大线程数 或 工作队列超过了设置的最大capacity能力的情况下,execute(Runnable)方法提交的任务将会被拒绝(不会执行)!当上述3种情况的任意一种情况发生时,会回调RejectedExecutionHandler接口的rejectedExecution(Runnable, ThreadPoolExecutor)方法,可以实现RejectedExecutionHandler接口的rejectedExecution(Runnable, ThreadPoolExecutor)方法,在其中做一些抛出异常或日志记录的操作!

3 线程池工作示例图

(1)当一个任务通过submit或者execute方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于coolPoolSize,即使其他工作线程是空闲的,也会创建一个新线程来处理该任务

(2)如果当前池中线程数大于等于coolPoolSize个,则将该任务加入到等待队列。只要有核心线程空闲,那么就会从队列中去获取任务进行处理(重用空闲的核心线程);

(3)如果任务不能入队列,说明等待队列已满,若当前池中线程数小于maximumPoolSize,则创建一个临时线程(非核心线程)执行该任务。

(4)如果当前池中线程数已经等于maximumPoolSize,此时无法执行该任务,根据拒绝执行策略处理,后面还会详细讲解具体的拒绝执行策略。

      以上4步是线程池处理到达任务的主要流程。当池中线程数大于coolPoolSize,超过keepAlive时间的闲置线程会被回收掉。注意,回收的是非核心线程,核心线程一般是不会回收的。如果设置allowCoreThreadTimeOut(true),则核心线程在闲置keepAlive时间后也会被回收。任务队列是一个阻塞队列,核心线程执行完任务后会去队列取任务来执行,如果队列为空,线程就会阻塞,直到取到任务。

4  代码实现:初始化线程池,使用线程池跑多线程任务

package com.test.alltest.java.thread.threadPool;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

//线程池测试
public class Main {

    private static AtomicInteger count = new AtomicInteger(0);//线程计数
    private static int corePoolSize = 2;//核心线程数
    private static int maximumPoolSize = 4;//最大线程数。可以计算,非核心线程数 = maximumPoolSize - corePoolSize = 2
    private static long keepAliveTime = 10;//非核心空闲线程最长存活时间
    private static TimeUnit timeUnit = TimeUnit.SECONDS;//非核心空闲线程最长存活时间的单位
    //指定阻塞队列的类型和队列长度。可以计算,被核心线程数同时处理的任务个数 = corePoolSize + 队列容量 = 2 + 2 = 4
    private static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
    //定义线程工厂
    private static ThreadFactory myThreadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable runnable) {
            String threadName = "线程" + count.addAndGet(1);
            Thread t = new Thread(runnable, threadName);
            return t;
        }
    };
    //定义任务拒绝执行的策略
    private static RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 记录异常
            // 报警处理等
            int iActiveCount = executor.getActiveCount();
            int iRemainQueue = executor.getQueue().remainingCapacity();
            int iexsitQueue = executor.getQueue().size();
            System.out.println("Warn:当前运行的线程数:"+String.valueOf(iActiveCount) +
                    ",当前队列剩余数:" + iRemainQueue +
                    ",当前队列排队的任务数:" + iexsitQueue);
        }
    };

    public static void main(String[] args) {
        //1.初始化线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                timeUnit,
                workQueue,
                myThreadFactory,
                rejectedExecutionHandler
                );

        /**
         * 2.使用线程池
         * 创建7个任务: 根据线程池的初始化参数配置可推测:
         * (1)2个任务立即执行;
         * (2)2个任务加入到阻塞队列中等待核心线程执行;
         * (3)2个任务被2个非核心线程执行;
         * (4)剩下的1个任务没有线程来执行(被拒绝执行),因此调用【拒绝执行策略】来处理。
         */
        for(int i=0; i<7; i++){
            //执行任务
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("当前执行任务的线程名:" + Thread.currentThread().getName());
                    long start = System.currentTimeMillis();
                    while (true){
                        long end = System.currentTimeMillis();
                        if((end - start) > 10000){//10秒
                            break;
                        }
                    }
                }
            });
        }
    }
}

Tips:

      1)将CorePoolSize和maximumPoolSize值设置成一样,即:创建一个固定大小的线程池.

      2)将maximumPoolSize设置成最大值(Integer.MAX_VALUE),表示设置线程池可以容纳任意数量的并发任务(即:任意数量的线程)。

      3)大多数情况下,核心(CorePoolSize)和最大线程(maximumPoolSize)池的大小在构造线程池对象的时候进行设置(构造方法中设置);但是也可以使用{setCorePoolSize}和{setMaximumPoolSize}方法动态地更改它们。

如果以上教程对您有帮助,为了不迷路,请关注一下吧~

猜你喜欢

转载自blog.csdn.net/IT_Most/article/details/108719730
今日推荐