暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

NO.30 掰开揉碎 :线程池的工作机理

技术夜未眠 2017-12-28
179


点击上方蓝字,加入读书践行群,最【好学】的程序员都在这里!


碎片时间|体系学习

这是程序员Chatbook第84篇原创

距离2018年还有3


 今日难度系数 :🌟🌟🌟

预计阅读时间 : 3 分钟



00、从一个例子谈起


时间:为了能更加说明问题,让我们把时间退回到10年前,那个时候还没有所谓的排队叫号服务,为了办理某种业务还需要排队的那个年代。


场景:某地的某银行营业厅柜台开了8个窗口,每个窗口里面都坐了一位工作人员;当储蓄用户来到银行大厅后,看见有窗口是空闲的,那么就可以直接前往空闲的窗口去办理业务;慢慢地,到银行办理储蓄业务的人越来越多了,8个窗口都在对外提供服务;这个时候,新到的客户就只能进行排队


如果新来客户的增长速度远远大于银行工作人员完成一项客户业务的速度,这样客户排队就会越积越多。这时候,银行的大堂经理可能会增开2个临时窗口为排队的客户提供服务。


那知道今天是除夕,过来办理储蓄业务客户越来越多,10(8+2)个窗口也不够用了,这个时候大堂经理可能就要考虑不再接收新客户或规劝(抛弃)现在排队的客户去就近的银行营业厅去办理业务了。


时间到了下午,过来办理储蓄业务的客户越来越少了(可能大家都去准备年夜饭了),大堂经理发现新增的2个临时窗口空闲后,又等待观察了10分钟后,发现空闲依旧,可能就要考虑把之前的2个临时窗口关闭了,只保留之前的8个窗口。


好了,例子讲完了,老铁们一定想到了,上述的例子只是对线程池工作过程的一个形象化比喻。下面我们请ThreadPoolSize再次登场,如代码1所示:


1

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,
               TimeUnit unit,

BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler);

复制


代码1中是ThreadPoolExecutor的构造函数,其参数含义如下:

  • corePoolSize:指定了线程池中的线程数量;指代“上例中的8个窗口”

  • maximumPoolSize:指定了线程池中的最大线程数量;指代“上例中的10个窗口(8个正式窗口+2个新增临时窗口)”

  • keepAliveTime:当线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间。即:超过corePoolSize的空闲线程,在多长时间内,会被销毁。指代“大堂经理发现新增的2个临时窗口空闲后,又等待观察了10分钟”中的数值10

  • unit:keepAliveTime的单位;指代“上例中的10分钟”中的分钟

  • workQueue:任务队列,被提交但尚未被执行的任务;指代“上例中的10个窗口”

  • threadFactory:线程工厂,用于创建线程,一般用默认的即可;后续会专题讨论

  • handler:拒绝策略。当任务太多来不及处理,如何拒绝任务;后续会专题讨论


01、进一步分析


通过上例的场景代入,我们再对线程池进行深入分析。


线程池中有两个核心参数,很容易弄错,我们再辨析一下:

  • corePoolSize:该值设定好以后,默认情况下并不会在线程池中创建任何线程,而是等待有任务来时才开始创建线程;只有显式调用了prestartAllCoreThreads方法后,线程池才会预先创建corePoolSize个线程。也就是说,默认情况下创建的线程池,线程池中的线程数为0,当有任务来之后,才会创建以线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

  • maximumPoolSize:它表示线程池中最多能创建多少个线程。

  • 达到(corePoolSize+1)数目之前的线程并不会被线程池释放销毁,但是从(corePoolSize+1)~ maximumPoolSize之间的创建的线程会被释放销毁。


文章的最后,我们通过ThreadPoolExecutor的执行调度程序源代码,分析一下向线程池提交作业的时候,到底发生了什么,见代码2;图1说明了线程池的执行过程。

2

public void execute(Runnable command) {

   if (command == null)

        throw new NullPointerException();

    int c = ctl.get();

    //利用workerCountOf获取当前线程池的线程总数

    //如果小于corePoolSize,调用addWorker调度线程执行

    if (workerCountOf(c) < corePoolSize) {

        if (addWorker(command, true))

            return;

        c = ctl.get();

   }

    //如果大于corePoolSize,则调用workQueue.offer

    //将任务进入等待队列

    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);

    }

   //如果进入等待队列失败,则将任务提交给线程池

   //如果线程池数目已经达到maximumPoolSize,则执行拒绝策略

   else if (!addWorker(command, false))

       reject(command);

}

复制



图1 线程池执行调度过程


【参考资料】

1 Java多线程编程实战指南, 黄文海, 中国工信出版集团。

2 实战Java高并发程序设计, 葛一鸣,郭超, 中国工信出版集团,电子工业出版社。


有效地利用您的碎片化时间,每日精进;独行快,众行远!想了解更多软件开发技术与人生感悟分享,请长按二维码图片,关注程序员chatbook。如果觉得文章有用,同时欢迎并感谢您能把文章分享给有需要的伙伴,大家共同进步。




程序员Chatbook

          

程序员都关注了,来不及解释,长按图片,快上车




每日一句

早不起能误一天事,少不学能误一生事。

——蒙古谚语



本文延伸阅读

知识点1:Java内存模型

NO.27  编写高并发程序:Java内存模型


知识点2:编写高并发程序的“正确姿势”

NO.28  编写高并发程序:深挖洞


知识点3:什么是线程池

NO.29  有容乃大:线程池


知识点4:内部类的类型、实现方式及应用场景

NO.20  朝中有人好办事:内部类


推荐1:习惯决定命运,高效程序员的习惯

推荐2:编写可读代码的艺术



文章转载自技术夜未眠,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论