好玩的不可思议的ThreadPool线程池

好玩的、不可思议的ThreadPool线程池

JDK1.5为我们新增了两种实现多线程的方式:线程池和Callable接口,本次我们来玩一下线程池。

首先,介绍一下ThreadPoolExecutor的构造函数及其7个参数的内容:

在这里插入图片描述

其次,我们来看一下线程池执行任务的流程:

ThreadPoolExecutor源码对execute方法的说明:

在这里插入图片描述

其本质就是ThreadPool线程池的执行任务流程,用流程图示意如下:

在这里插入图片描述

在单轮次的多个任务过来时,线程池中的线程执行任务时完全符合上述规律。

那么,如果我们在线程池执行完一轮任务后,再来一轮的任务呢?线程池中的现有线程(包括核心线程和非核心线程)该如何执行这一轮次的任务呢?

下面我写了代码进行测试,我把核心线程数设置为2个,最大可扩展线程数设置为4个,非核心线程数空闲生存时间设置为2s,阻塞队列设置为3个。

测试1:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(2, 4, 2, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            for (int i = 0; i < 7; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程池");
                });
            }

            TimeUnit.MILLISECONDS.sleep(500);

            System.out.println("=================第二轮线程==================");
            for (int i = 0; i < 4; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程池");
                });
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

测试结果如下:

在这里插入图片描述

按照常理来想,那么第一轮结束后有4个线程已经空闲下来了,那么第二轮任务过来时应该是这些空闲下来的任务直接接收任务,然后执行任务吧。但是测试结果呈现的显然不是我们预想的。

为了弄清楚其底层的原理,我们先来剖析一下,在第二轮任务来临时,线程池现有4个线程处于活跃状态,阻塞队列容量为3,现在处于空的状态。

第二轮有4个任务,但是线程只执行了3个任务,有1任务执行了拒绝策略。

明明线程池中现在有4个线程是处于活跃状态的,为什么不将4个任务全部执行,反而要将其中1个任务拒绝呢?

那么,我又进行了第二次测试。

测试2:

这次测试,我让非核心线程在空闲2秒后失效(其他代码不变),那么,线程池在即将执行第二轮任务时,线程池中只剩下核心线程(2个),并且阻塞队列(容量为3)也是空的。

将上述测试代码中的

TimeUnit.MILLISECONDS.sleep(500);

修改为:

TimeUnit.MILLISECONDS.sleep(2500);

然后执行,测试的结果如下:

在这里插入图片描述

本次测试,在第二轮任务来临时,线程池现有2个线程处于活跃状态,阻塞队列容量为3,而且处于空闲状态。

这次却将4个任务全部执行了。

原因分析

仔细查看源码及其文档说明后,也没有发现问题。

经过仔细思考,发现线程池的执行流程官方文档所写的是正确的(线程池的执行任务流程示意图表示的流程也是正确的)。因为当多个任务来临时,线程池不是首先判断是否有空闲的线程(execute方法根本就没有这个流程,这样的设计完全不符合人的思维习惯),而是直接判定线程数是否达到核心线程数,如果已达到,则进入阻塞队列,如果阻塞队列已满,再进行判定线程数是否已达到最大可扩展线程数,如果已达到则执行拒绝策略。

详细分析如下:
在测试1中即将执行第二轮任务时的线程池的状态如下:

线程池现有4个线程处于活跃状态,阻塞队列容量为3,现在处于空的状态。

当第二轮任务过来后,

1.判定现有线程数(目前是4)是否达到核心线程数(2),结果是已达到,进行下一步判定;

2.判定阻塞队列是否已满,目前任务是4个,阻塞队列的容量是3个,所以其中有3个任务放入了阻塞队列,另外1个任务需要进行下一步的判定;

3.判定现有线程数(目前是4)是否已达到最大可扩展线程数(4),结果是已达到,那么最后1个任务被执行拒绝策略。

在测试2即将执行第二轮任务时线程池的状态如下:

线程池现有2个线程处于活跃状态,阻塞队列容量为3,现在处于空的状态。

当第二轮任务过来后,

1.判定现有线程数(目前是2)是否达到核心线程数(2),结果是已达到,进行下一步判定;

2.判定阻塞队列是否已满,目前任务是4个,阻塞队列的容量是3个,所以其中有3个任务放入了阻塞队列,另外1个任务需要进行下一步的判定;

3.判定现有线程数(目前是2)是否已达到最大可扩展线程数(4),结果是未达到,那么线程池创建新的线程,并执行这最后1个任务。

那么我们得出结论:

线程池的工作流程如下:

1.判定现有线程数是否达到核心线程数,如果未达到,则创建线程并执行任务;

2.如果现有线程数已达到核心线程数,则判定阻塞队列是否已满,未满则进入阻塞队列;

3.如果阻塞队列已满,则判定现有线程数是否已达到最大可扩展线程数,如果未达到,则创建线程并执行任务;

4.如果现有线程数已达到最大可扩展线程数则执行拒绝策略。

官方文档的说明的和源码写的是一致的,经测试也没有问题,只是线程池的这种工作流程不符合人类的思维习惯。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值