zoukankan      html  css  js  c++  java
  • 线程池原理讲解——ThreadPoolExecutor

    【这是前几天的存货,留着没发表,今天又复习一遍,润化了部分内容,继续干】

    说线程池前,先简单回顾一下线程的状态吧:

    1、线程状态转换

    线程的五种状态,及其转换关系:

    2、线程创建方式

     三种:两个接口一个类

    两个接口:Runnable实现run(), callable实现call()

    一个类:Thread实现run()

    《阿里巴巴Java手册》中规定:

    接下来进入正题:

    3、线程池ThreadPoolExecutor实现原理

    3.1、ThreadPoolExecutor中定义了几个线程池状态常量

    // runState is stored in the high-order bits
        private static final int RUNNING    = -1 << COUNT_BITS;
        private static final int SHUTDOWN   =  0 << COUNT_BITS;
        private static final int STOP       =  1 << COUNT_BITS;
        private static final int TIDYING    =  2 << COUNT_BITS;
        private static final int TERMINATED =  3 << COUNT_BITS;
    • RUNNING:是运行状态,线程池可以接收新任务;
    • SHUTDOWN:是在调用shutdown()方法以后处在的状态。表示不再接收新任务,但队列中的任务可以执行完毕;
    • STOP:是在调用shutdownNow()方法以后的状态。不再接收新任务,中断正在执行的任务,抛弃队列中的任务;
    • TIDYING:表示所有任务都执行完毕;
    • TERMINATED:为中止状态,调用terminated()方法后,尝试更新为此状态;

    3.2、 构造方法

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory, RejectedExecutionHandler handler) {        
        }
    • corePoolSize是线程池的核心线程数
    • maximumPoolSize是线程池允许的最大线程数
    • keepAliveTime为线程空闲时的存活时间
    • unit是keepAliveTime的单位
    • workQueue是用来保存等待被执行的线程的队列
    • threadFactory,线程工厂,通常使用默认工厂,定义了线程名称生成规则。
    • handler是当最大线程数和队列都满了以后,线程池的处理策略

    3.3、Executors工厂类

    JDK提供了Executors工厂类。这里面有几种实例化线程池的方法:

    • CachedThreadPool():可缓存线程池,一个任务创建一个线程,最大线程数为Integer.MAX_VALUE,所以使用时要控制好并发数量,否则创建过多的线程会占用大量资源;
    • FixedThreadPool():指定数线程池,所有任务只能使用数量固定大小的线程;
    • SingleThreadExecutor():单一数线程池,即相当于大小为1的FixedThreadPool(),可保证提交任务的顺序执行;
    • ScheduleThreadPool():周期性线程池,可固定时间执行,也可延时执行,最大线程数为Integer.MAX_VALUE,。

    上述虽然很方便,但是!

    但是《阿里巴巴Java手册》又说了:在开发中创建线程池时,最好不要使用Executor类中的初始化方法来创建,而是使用ThreadPoolExecutor类中的初始化来创建,这样的好处是能够让写的同学更明确线程池的运行规则,从而避免资源耗尽的风险。

    而使用Executor返回的线程池对象的弊端如下:

    1)FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;

    2)CachedThreadPool 和ScheduleThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

    3.4、ThreadPoolExecutor执行任务的方法【最重要,也是线程池的核心原理】

    public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            int c = ctl.get();
            if (workerCountOf(c) < corePoolSize) {
                if (addWorker(command, true))
                    return;
                c = ctl.get();
            }
            if (isRunning(c) && workQueue.offer(command)) {
                int recheck = ctl.get();
                if (! isRunning(recheck) && remove(command))
                    reject(command);
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
            else if (!addWorker(command, false))
                reject(command);
        }

    代码看不懂不要紧,一个例子附上来:

    假如我手里有500W RMB(哈哈哈,想暴富想疯了),俗话说“富不过三代”,心里想着不能座山吃空啊,还得好好想想经营之道来用钱生钱。

    由于本人对吃的,对干饭比较感兴趣,于是乎,就准备开一个饭店。取名为“池饭的店”。我的饭店还有个特色就是一对一服务,一个厨子服务一桌顾客噢,哇好高级。

    开饭店得有厨子对吧,那好,我斥“巨”资请来了5位大厨,,都烧得一手红烧肉(哈哈哈,吃肉肉也算特色)。

    开业以后生意良好,但我巡逻发现好多顾客更喜欢吃辣的,为了赚更多钱,于是我又一狠心请了5位川菜大厨。

    一段时间后,饭店生意越来愈好,顾客也越来越多。有厨子说得再加几个厨子才行,但是作为老板,我要赚钱啊,得控制成本(哈哈哈,资本的世界),我发现不能再招厨子了。因为光这10位常用厨子的开支都已经快占了收入的一半了。那厨子抱怨顾客多,忙不过来怎么办呢?哈哈哈,我说:“顾客多,那就让顾客排队等着,谁让你们几位大厨做的饭菜香呢(哈哈哈,霸气的同时也要拍一下大厨的马屁,得让他们舒舒服服地听我的话)”。

    于是,当顾客多了后,没有空闲的厨子进行一对一服务了,多的顾客就排号等着。等哪位顾客吃完了,厨子服务空闲了,就叫下一桌。好办法,果然平时生意井然有序,厨子不慌不忙也还算可以。

    但是呢?我巡逻又发现,周一到周四还可以,但是一到周末,饭店周围的大佬们都休息,但是又懒得做饭,就全部都涌来到我的饭店了,毕竟服务周到嘛。这时候有些顾客就不满意了,就抱怨人太多,等一次都要一个小时以上,不划算。于是,为了挽留住这些客源,我每到周末就从其他地方雇来10位临时厨子,过了周末再把他们辞了(不要怪我狠心,都是出来挣票票的)。这样周末就有了20位厨子,但平时周一周五还是原来10个厨子,毕竟不养闲人嘛。哈哈哈,好办法。

    虽然这样有所缓解,大家周末排队的时间从1h减少为0.5h,但是有时候还是会顾客饱满,没办法啊,生意太好了,哈哈哈。那怎么办呢?我的饭店空间就这么大,不可能无限增加厨子啊,那好吧,为了不让厨子抱怨,不让厨子累着,那我只能忍痛割爱,残忍地将部分顾客拒之门外,同时赔送点小礼品,代金券。

    哈哈哈,后来想了想,既然客源这么多,那为了赚更多票票,我就开分店吧,于是我带领我的员工走上了人生巅峰!

    成功后,有人开始请我去讲解成功之道,我想了想,画了这个图:

    哈哈哈,如果你了解了我这个开饭店的“成功之道”,那么线程池你也就理解了。如下:

     好了,为了让大家更理解透彻,我给代码加上注释:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //获取clt,clt记录着线程池状态和运行线程数。
        int c = ctl.get();
        //运行线程数小于核心线程数时,创建线程放入线程池中,并且运行当前任务。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            //创建线程失败,重新获取clt。
            c = ctl.get();
        }
        //线程池是运行状态并且运行线程大于核心线程数时,把任务放入队列中。
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //重新检查线程池不是运行状态时,
            //把任务移除队列,并通过拒绝策略对该任务进行处理。
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //当前运行线程数为0时,创建线程加入线程池中。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //运行线程大于核心线程数时并且队列已满时,
        //创建线程放入线程池中,并且运行当前任务。
        else if (!addWorker(command, false))
            //运行线程大于最大线程数时,失败则拒绝该任务
            reject(command);
    }

    里面多次调用的addWorker()方法如下:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            //获取clt,clt记录着线程池状态和运行线程数。
            int c = ctl.get();
            //获取线程池的运行状态。
            int rs = runStateOf(c);
    
            //线程池处于关闭状态,或者当前任务为null
            //或者队列不为空,则直接返回失败。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
    
            for (;;) {
                //获取线程池中的线程数
                int wc = workerCountOf(c);
                //线程数超过CAPACITY,则返回false;
                //这里的core是addWorker方法的第二个参数,
                //如果为true则根据核心线程数进行比较,
                //如果为false则根据最大线程数进行比较。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //尝试增加线程数,如果成功,则跳出第一个for循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //如果增加线程数失败,则重新获取ctl
                c = ctl.get();
                //如果当前的运行状态不等于rs,说明状态已被改变,
                //返回第一个for循环继续执行
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
    
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //根据当前任务来创建Worker对象
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    //获得锁以后,重新检查线程池状态
                    int rs = runStateOf(ctl.get());
    
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        //把刚刚创建的线程加入到线程池中
                        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;
    }

    各位看官,理解否?

    Over.....

    参考:

    1、给女朋友讲 : Java线程池的内部原理

    2、线程池ThreadPoolExecutor实现原理

  • 相关阅读:
    图片验证码制作
    上传图片加水印
    组合查询加分页
    C# 数据类型 数据转换 自己的见解和方式
    C# 基础控制台程序的创建,输出,输入,定义变量,变量赋值,值覆盖,值拼接,值打印
    关于Spring注解
    java I/O
    关于web.xml配置
    第7章 使用springMVC构建Web应用程序 7.1 springMVC配置
    js配合c3制作一个动态钟表
  • 原文地址:https://www.cnblogs.com/gjmhome/p/14393029.html
Copyright © 2011-2022 走看看