池化技术之线程池

一、背景

前段时间,做了一个用户自动领券的功能,进入小程序自动给用户发放符合条件的券。由于该接口是用户进入小程序必调用的接口,且不需要用户有任何的感知,考虑用户体验觉得这个场景非常适合做异步,然后在多考虑一点性能,线程的创建和销毁等带来的一点开销大家很容易想到线程池来统一管理,使用线程池都逃脱不了那几个参数的定义,当时设置参数时,只明确的知道它是吃IO的和拒绝策略,然后当被问道我定义这几个数字的依据是什么,其实当初我只是凭一种感觉,觉得这样问题不大、够用,没有去了解过系统用户通常的活跃量,也更没有去好好读过线程池的源码。

二、线程池核心参数

java 复制代码
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize: 核心线程数, 常驻在线程池中的工作线程,默认情况(allowCoreThreadTimeOut = false)不会被回收

allowCoreThreadTimeOut=true,可动态的回收核心线程资源,此时空闲回收时间由keepAliveTime控制,这种情况下,当有新任务到达时,可能需要重新创建一个新的核心线程来处理任务,存在系统不端的创建和销毁核心线程

  • maximumPoolSize:最大线程数,线程池中最大容纳的线程数量(扩容机制)

  • keepAliveTime:allowCoreThreadTimeOut = false时,非核心线程回收的条件

  • TimeUnit :配合keepAliveTime一起使用

  • BlockingQueue:存储任务的容器

  • ThreadFactory:创建线程的工厂,可设置线程组、线程优先级、尤其名字方便排错

  • RejectedExecutionHandler:超过线程池能够处理的容量保护机制 核心线程都满,任务队列也满,也达到了最大线程,提交的任务会走拒绝策略

注:且java 提供Executors创建线程池方式,不要使用,了解即可,因为它对线程的控制粒度很低,很多重要参数比如核心线程数,最大线程数,拒绝策略等自己无法控制,建议手动创建线程池

三、线程池属性标识及状态流转

java 复制代码
    //int类型32bit 1、高三位代表线程池的状态;2、低29位线程池中的线程个数
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //29 方便后面做位运行
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //位运算得出线程池最大容量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    // 线程池的5种状态
    //运行状态,能够接受新的任务且会处理阻塞队列中的任务
    private static final int RUNNING    = -1 << COUNT_BITS;
    //关闭状态,不接受新任务,但是会处理阻塞队列中的任务
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //停止状态,不接受新的任务,也不会处理等待队列中的任务并且会中断正在执行的任务
    private static final int STOP       =  1 << COUNT_BITS;
    // 整理,即所有的任务都停止了,线程池中线程数量等于0
    private static final int TIDYING    =  2 << COUNT_BITS;
    //结束状态,terminated()方法执行完了
    private static final int TERMINATED =  3 << COUNT_BITS;
	// 线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
	//线程池线程数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }	

四、JDK线程池执行流程

java 复制代码
    /**
     * 提交任务
     *
     * @param command 任务
     */
    public void execute(Runnable command) {
       //.....省略空判断校验代码、健壮性
       //int类型32bit 1、高三位代表线程池的状态;2、低29位线程池中的线程个数
        int c = ctl.get();
        //1、创建核心线程数,工作线程数<核心线程数
        if (workerCountOf(c) < corePoolSize) {
            //工作线程数<核心线程数,添加一个工作线程为core=true 核心线程,执行提交的任务
            if (addWorker(command, true)) {
                //此提交任务结束
                return;
            }
            //(前面if失败,没有return结束,并发问题)核心线程创建失败,重新获取32位的值
            c = ctl.get();
        }
        //2、线程池中的线程数量大于等于核心线程数
        //先判断线程池为running状态(因为shutdown和stop状态线程池是不接收任务的),将任务添加到阻塞队列中
        if (isRunning(c) && workQueue.offer(command)) {
            //获取 ctl
            int recheck = ctl.get();
            // 判断是否是RUNNING状态,不是则移除此次提交的任务,(并发问题,running进来的,非running不接收此次提交的任务)
            if (!isRunning(recheck) && remove(command)) {
                //走拒绝策略
                reject(command);
                //如果处于RUNNING状态,并且线程池中正在工作线程数为0,但阻塞队列有任务
            } else if (workerCountOf(recheck) == 0) {
                //(并发,if running状态进来的,且加入阻塞队列)的 任务,阻塞队列有任务但没有工作线程,则添加一个任务为空的工作线程处理,避免阻塞队列中的任务没有线程处理
                addWorker(null, false);
            }
            // 队列已满,创建非核心线程core=false处理任务
        } else if (!addWorker(command, false)) {
            //创建非核心线程失败(最大线程已满),走拒绝策略
            reject(command);
        }
    }

jdk线程池和tomcat线程池区别

  • jdk线程池由源码分析提交任务的第一个第一个if判断,当new 一个线程池后,没有提交任务时,池里面是没有线程的,懒创建过程,tomcat线程池在项目启动时,核心线程数会默认创建的
  • 当核心线程数已满都在工作时,有新的任务提交过来时,jdk线程池在任务队列没满时会优先将改任务放进队列,队列已满才创建最大线程;而tomcat线程池在工作线程没有达到最大线程数时会优先创建最大线程数,最大线程也满了在放入队列,相反过程
  • jdk线程池尽量保证核心线程处于工作状态,核心线程处理任务,实在不够放进队列,tomcat线程池主要处理网络请求,属于IO密集型,支持更改吞吐量,尽量去保证所有的请求都能得到立即处理

五、Worker封装

线程池中的线程,都会被封装成一个Worker类对象,ThreadPoolExecutor维护的其实就是一组Worker对象,其中用集合workers存储这些Worker对象; Worker 的有参构造传入了我们的任务,并用线程工厂的方式创建了线程,将work自己传入,线程启动start,会调用run方法,run() 方法调用了 runWorker(this),将自己传入进去

java 复制代码
  private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
        final Thread thread;
        Runnable firstTask;
        volatile long completedTasks;

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        public void run() {
            runWorker(this);
        }
}

思考:线程常驻与销毁

  • 当提交一个任务,工作线程数量未达到核心线程时会创建一个核心线程,任务执行完毕,没有新的任务提交,此核心线程如何常驻在线程池中?
  • 当核心线程没有空闲且阻塞队列已满且工作线程未达到最大线程时,会创建一个非核心线程执行该任务,任务执行完毕,没有新的任务提交过来,该非核心线程空闲达到超时时间,此非核心线程如何销毁?

1、addWorker

java 复制代码
 private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //双层for循环目的主要是工作线程数量标识+1
        for (;;) {
            int c = ctl.get();
            //线程池状态
            int rs = runStateOf(c);
            // 除了RUNNING状态,
            if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&  firstTask == null  && ! workQueue.isEmpty()) ){
                // 线程池如果不是SHUTDOHN,就代表是STOP或者更高的状态,这时,不需要添加线程处
                //如果任务为nul1, 并且线程池状态不是RUNNING,不需要处理
                // 如果阻塞队列为空,返同false,外侧的!再次取反,获取true,不需要处理
                //创建工作线程失败
                return false;
              }  
            for (;;) {
                //工作线程个数
                int wc = workerCountOf(c);
                //当前工作线程>=最大线程数 || wc >=最大线程数或者核心线程数
                if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize)){
                    //创建工作线程失败
                    return false;
                   }
                   //CAS成功 工作线程+1
                if (compareAndIncrementWorkerCount(c))
                    //退出外侧循环
                    break retry;
                    //CAS失败重新判断线程池状态
                c = ctl.get();  // Re-read ctl
                 //和最开始外侧获取的状态发生变化,结束此外侧循环,继续下一次外侧循环
                if (runStateOf(c) != rs)
                   //结束外侧循环
                    continue retry;
                    //否则继续下一次内侧循环
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        //双层for循环工作线程数量+1 成功
      //工作线程默认开始失败
        boolean workerStarted = false;
        //工作线程默认添加失败
        boolean workerAdded = false;
        //工作线程
        Worker w = null;
        try {
            //将我们的任务传入工作线程
            w = new Worker(firstTask);
            //从worker对象活动线程对象
            final Thread t = w.thread;
            if (t != null) {
                //线程池全局锁,避免添加任务时,其它线程干掉该线程池
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
                    //线程池状态RUNNING
                    if (rs < SHUTDOWN ||
                    //线程池SHUTDOWN创建一个空任务处理阻塞队列中的任务时
                        (rs == SHUTDOWN && firstTask == null)) {
                        //线程是运行状态
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //工作线程添加到ashSet<Worker>
                        workers.add(w);
                        int s = workers.size();
                        //工作线程数大于记录的最大工作线程数,交换值
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        //工作线程添加成功
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //工作线程添加成功
                if (workerAdded) {
                    //启动工作线程
                    t.start();
                    //启动工作线程成功
                    workerStarted = true;
                }
            }
        } finally {
            //工作线程启动失败
            if (! workerStarted)
                addWorkerFailed(w);
        }
        //返回工作线程是否启动成功
        return workerStarted;
    }

2、runWorker

java 复制代码
  final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //当前工作线程的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        //WORk构造涉及的AQS,暂不扩展
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //循环读取线程池中任务队列并执行
            //第一次循环 task不为空,直接进入循环体,执行任务,执行完毕将 task = null
            //第二次循环 task=null,||线程池的中的任务执行完毕 执行getTask()方法,从线程池阻塞任务队列中获取任务,可能会阻塞实现线程的复用,并执行该任务
            while (task != null || (task = getTask()) != null) {
              //加锁,避免线程池shutdown,任务也不中断
                w.lock();
                //状态TIDYING或者TERMINATED
                if ((runStateAtLeast(ctl.get(), STOP) ||
                        (Thread.interrupted() &&
                                runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //执行任务前的操作,根据自己业务重写扩展
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //执行任务
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                      //执行任务前的操作,根据自己业务重写扩展
                        afterExecute(task, thrown);
                    }
                } finally {
                    //执行完毕最后将task=null
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            // getTask()=null 核心线程>最大线程时,以超时的时间阻塞住获取不到任务,超时时间一过跳出循环
            completedAbruptly = false;
        } finally {
            //获取不到任务的线程,执行该方法
            processWorkerExit(w, completedAbruptly);
        }
    }

3、getTask

java 复制代码
 private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            //线程池中的线程数
            int wc = workerCountOf(c);

            // Are workers subject to culling?
            //线程池中的线程数>大于核心线程数,timed=true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                        //线程池中的线程数>核心线程数,带有超时时间的方式阻塞住
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        //线程池中的线程数<=核心线程数,队列中没有任务拿取不到会无限阻塞,直到队列有任务,拿取继续执行任务
                        workQueue.take();
                if (r != null){
                    return r;
                }
                //线程池中的线程数>核心线程数,带有超时时间,超时获取不到任务,此时getTask获取不到任务
                timedOut = true;
            } catch (InterruptedException retry) {
               //阻塞的线程,被中断
                timedOut = false;
            }
        }
    }

4、processWorkerExit

java 复制代码
 /**
     * 获取不到任务的线程执行该方法
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
         //线程池的状态,不是stop状态,即线程中断,直接结束,退出,工作线程清理
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                //拿取核心线程数
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty()){
                    min = 1;
                }
                //当前线程数>=核心线程数
                if (workerCountOf(c) >= min){
                    //直接销毁本线程,不会创建新的线程
                    return; // replacement not needed
                }
            }
            //本线程直接销毁,新开一个没有任务的线程
            addWorker(null, false);
        }
    }
相关推荐
海绵波波1072 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
网络风云3 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999063 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
运维&陈同学5 小时前
【Kibana01】企业级日志分析系统ELK之Kibana的安装与介绍
运维·后端·elk·elasticsearch·云原生·自动化·kibana·日志收集
Javatutouhouduan7 小时前
如何系统全面地自学Java语言?
java·后端·程序员·编程·架构师·自学·java八股文
后端转全栈_小伵7 小时前
MySQL外键类型与应用场景总结:优缺点一目了然
数据库·后端·sql·mysql·学习方法
编码浪子8 小时前
Springboot高并发乐观锁
后端·restful
uccs8 小时前
go 第三方库源码解读---go-errorlint
后端·go
Mr.朱鹏9 小时前
操作002:HelloWorld
java·后端·spring·rabbitmq·maven·intellij-idea·java-rabbitmq
编程洪同学10 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端