Featured image of post 玩转Java线程池一:ThreadPoolExecutor的执行流程和原理

玩转Java线程池一:ThreadPoolExecutor的执行流程和原理

一、为什么要用线程池

在现实开发中是不会手动地去new一个线程出来用的,阿里巴巴的Java开发手册中也写到不要手动创建线程,要使用线程池。几乎所有的需要异步或者并发执行的程序都可以使用线程池。使用线程池有三个好处:

  • 降低资源的消耗:在线程池中有一些已经创建好的线程,通过重复利用这些线程,可以减低线程的创建和销毁造成的消耗。
  • 提高响应速度:当要执行的任务达到之后,不需要去创建线程池,可以立即执行。
  • 提高线程的可管理性:线程是珍贵的资源,要是重量级的。不能无限制地去创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一管理、调优和监控线程。

二、线程池的处理流程

当提交一个新任务到线程池时,线程池的处理流程如下:

1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。 3、线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

如下图:

Java中线程池的类是ThreadPoolExecutor,里面执行的方法是execute方法,主要的execute的执行流程如下:

总得来说,ThreadPoolExecutor的execute方法大致的设计思路是这样的:

1、如果当前线程池运行的线程少于corePoolSize,那么就创建一个线程来执行提交过来的任务。(此操作需要获取全局锁)

2、如果当前线程池运行的线程等于或者大于corePoolSize,那么提交过来任务会被放置在BlockingQueue(阻塞队列)中。

3、如果BlockingQueue(阻塞队列)已满,那么就创建新的线程来处任务(此操作需要获取全局锁)

4、如果撞见新的线程之后,线程池中的线程数大于maximumPoolSize,提交过来的任务会被拒绝执行,并执行拒绝策略

三、execute方法源码解析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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();
        }
    	// 如果线程池处于RUNNING状态,把任务添加到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            // 二次检查
            int recheck = ctl.get();
            // 如果当前线程池状态不是RUNNING则从队列中删除任务,并且执行线程池的拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果当前线程池为空,则创建一个线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
    	// 如果队列满了,则新增线程,新增线程失败则执行线程池的拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }