线程池

  • 一、什么是线程池?
  • 二、为什么要使用线程池?
  • 三、线程池配置参数
  • 1、核心线程池的实现
  • ① 核心属性
  • ③ 线程池运行状态 - 生命周期控制
  • ④ 全参构造方法
  • 2、超负载:拒绝策略
  • 3、线程池任务分配方案 - execute()方法
  • 四、创建 ThreadPoolExecutor 线程池 相关方法
  • 1、newCachedThreadPool() - 缓存线程池
  • 2、newFixedThreadPool() - 定长线程池
  • 3、newScheduledThreadPool() - 周期性定长线程池
  • 4、newSingleThreadExecutor() - 单任务线程池
  • 5、newSingleThreadScheduledExecutor() - 周期性单任务线程池
  • 五、合理的线程池大小
  • 六、分治:Fork / Join 框架
  • ※ ForkJoinPool线程池
  • ※ newWorkStealingPool()


一、什么是线程池?

为避免频繁地创建和销毁线程,我们可以复用创建的线程,具体操作为:

  • 维护一些线程(数量固定),让他们处于活跃状态,当需要使用线程时,直接获取一个连接即可。
  • 使用线程池后,创建线程变成了从线程池中获得空闲线程,关闭线程变成了向线程池归还线程,方便下次使用。

二、为什么要使用线程池?

创建线程池的目的:避免频繁地创建和销毁线程

为什么要避免频繁地创建和销毁线程?减少开销!

  • 线程创建、关闭需要 花费时间:使用过多线程可能会出现:创建、销毁线程占用时间>真实工作时间,得不偿失。
  • 线程本身 占用内存空间:大量线程会抢占内存,处理不当,会导致以下后果:
  • ① Out of Memory 异常
  • ② 回收大量线程,GC增压,延长GC停顿时间(若同时回收海量线程则问题更加严重)
  • 便于管理:使用线程池可以进行统一的分配、调优和监控。

所以我们必须对线程数量、使用过程加以管控,线程池技术就这样应运而生了。

三、线程池配置参数

线程池位于 JUCjava.util.concurrent

线程池概念图

android 定义全局线程池类库 java全局线程池_并发

1、核心线程池的实现

核心线程池 ThreadPoolExecutor 拥有许多关键属性、方法,理解它们对我们学习线程池十分重要,甚至可以尝试设计自己的线程池。

① 核心属性

android 定义全局线程池类库 java全局线程池_并发_02


变量ctl:一个32位的AtomicInteger类型的原子对象,记录两个关键信息:

  • 高 3 位:线程池运行状态
  • 低 29 位:线程池任务 (工作) 数量

主锁 - mainLock

主锁(mainLock),对关键方法提供 “锁” 支持,避免自身操作线程不安全

android 定义全局线程池类库 java全局线程池_并发_03

③ 线程池运行状态 - 生命周期控制

源码注释:

The runState provides the main lifecycle control, taking on values:
 RUNNING: Accept new tasks and process queued tasks
 SHUTDOWN: Don’t accept new tasks, but process queued tasks
 STOP: Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks
 TIDYING: All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
 TERMINATED: terminated() has completed

翻译注释:

英文名

中文译名

状态解释说明

RUNNING

【运行态】

可接收新任务,且可处理队列中的任务

SHUTDOWN

【关闭态】

不再接受新提交的任务,但可以继续处理正在执行的任务和队列中的任务

STOP

【停止态】

不再接受新提交的任务,也不处理队列中的任务,中断当前正在执行任务的线程

TIDYING

【休整态 / 等待态】

表示所有的任务已执行完毕,workerCount (有效线程数) 为0,但线程池仍未终止

TERMINATED

【终止态】

线程池彻底终止运行。

android 定义全局线程池类库 java全局线程池_android 定义全局线程池类库_04

状态转换:

android 定义全局线程池类库 java全局线程池_任务队列_05

线程状态

转换描述

解释说明

RUNNING (运行态)

-

初始线程池处于RUNNING状态,此时线程池中的任务为0

RUNNING (运行态) → SHUTDOWN (关闭态)

On invocationi of shutdown()

调用shutdown()方法

RUNNING (运行态) / SHUTDOWN (关闭态) → STOP (停止态)

On invocation of shutdownNow()

调用shutdownNow()方法

SHUTDOWN (关闭态) → TIDYING (休整态)

When both queue and pool are empty

任务队列、线程池均为空

STOP (停止态) → TIDYING (休整态)

When pool is empty

线程池为空

TIDYING (休整态) → TERMINATED (终止态)

When the terminated() hook method has completed

terminated方法执行完毕

④ 全参构造方法

核心线程池的全参数构造方法:

android 定义全局线程池类库 java全局线程池_android 定义全局线程池类库_06


构造方法参数含义、对应属性:

  • corePoolSize:指定线程池中的 核心线程 数量。
  • android 定义全局线程池类库 java全局线程池_android 定义全局线程池类库_07

  • maximumPoolSize:指定线程池中的最大线程数量(核心线程 + 非核心线程 数量)。
  • android 定义全局线程池类库 java全局线程池_任务队列_08

  • keepAliveTime:线程池线程数量超过corePoolSize时,额外新创建线程(非核心线程)的存活时间(即多长时间会被销毁)
  • unit:keepAliveTime的时间单位,可从枚举类 TimeUnit 中取出相应值。
  • android 定义全局线程池类库 java全局线程池_任务队列_09


  • android 定义全局线程池类库 java全局线程池_并发_10

  • workQueue:任务队列 / 等待队列(workQueue),用于存储被提交但尚未被执行(未被分配线程,如:任务数 > 最大线程数)的任务,方便向任务第一时间分配线程,执行的是FIFIO原则(先进先出)。
  • android 定义全局线程池类库 java全局线程池_任务队列_11

  • threadFactory:线程工厂,用于创建线程(可通过对应的set、get方法指定相应规则的工厂对象)
  • android 定义全局线程池类库 java全局线程池_java_12

  • handler:拒绝策略,定义任务过多难以应对时,拒绝(处理)任务的方式。
  • android 定义全局线程池类库 java全局线程池_java_13

2、超负载:拒绝策略

ThreadPoolExecutor类最后一个参数指定了拒绝策略,而 RejectedExecutionHandler 是拒绝策略的相关接口,通过实现本接口,可以创建自己的拒绝策略。

拒绝策略,即当任务数量超过系统实际承载能力时,采取的补救措施。

  • 通常由于压力太大而引起,线程池中的线程全部被占用,无法接纳新任务,同时等待队列也已经排满了,此时,引入一套解决问题的合理机制就显得尤为重要:

JDK内置四种拒绝策略:

android 定义全局线程池类库 java全局线程池_任务队列_14

  • AboutPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认方式。
  • CallRunsPolicy:直接调用execute()方法来执行当前任务。
  • DiscardPolicy:丢弃任务,但不抛出异常,也不予以任何处理。
  • DiscardOldestPolicy:丢弃最老(最先加入队列)的任务,再调用execute()将新任务添加进去。

3、线程池任务分配方案 - execute()方法

execute方法负责任务的分配工作:

  1. 判断核心线程池是否为空
  2. 判断任务队列是否有剩余空间
  3. 判断最大线程池是否有剩余线程数

    以上代码的 任务处理流程

四、创建 ThreadPoolExecutor 线程池 相关方法

1、newCachedThreadPool() - 缓存线程池

创建一个可缓存线程池,如有需要,可灵活回收空闲线程,若无可回收,则新建线程。
  先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

参数

解释说明

corePoolSize(核心线程数)

0

maximumPoolSize(最大线程数)

Integer.MAX_VALUE(过量创建可能会导致 Out of Memory)

keepAliveTime(非核心线程结束后存活时间)、unit(时间单位)

60秒

workQueue(任务队列)

SynchronousQueue<Runnable>

无缓冲等待队列

无参数方法:

android 定义全局线程池类库 java全局线程池_并发_15


可指定工厂的方法:

android 定义全局线程池类库 java全局线程池_线程池_16


测试缓存线程池:

public static void main(String[] args) {
    // 创建一个可缓存线程池
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        try {
            // sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cachedThreadPool.execute(new Runnable() {
            public void run() {
                // 打印正在执行的缓存线程信息
                System.out.println(Thread.currentThread().getName() + "正在被执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

android 定义全局线程池类库 java全局线程池_任务队列_17

2、newFixedThreadPool() - 定长线程池

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

参数

解释说明

corePoolSize(核心线程数)

传入参数

maximumPoolSize(最大线程数)

传入参数

keepAliveTime(非核心线程结束后存活时间)、unit(时间单位)

0毫秒

workQueue(任务队列)

LinkedBlockQueue<Runnable>

无界缓存任务队列

单参数方法:

android 定义全局线程池类库 java全局线程池_并发_18


可指定工厂的方法:

android 定义全局线程池类库 java全局线程池_java_19


测试定长线程池:

public static void main(String[] args) {
	// 创建一个可重用固定个数的线程池(定长为3)
	ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
	for (int i = 0; i < 10; i++) {
		fixedThreadPool.execute(new Runnable() {
			public void run() {
				try {
					// 打印正在执行的缓存线程信息
					System.out.println(Thread.currentThread().getName() + "正在被执行");
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
	}
}

android 定义全局线程池类库 java全局线程池_android 定义全局线程池类库_20

3、newScheduledThreadPool() - 周期性定长线程池

创建一个定长线程池,支持定时及周期性任务执行。

参数

解释说明

corePoolSize(核心线程数)

传入参数

maximumPoolSize(最大线程数)

Integer.MAX_VALUE(过量创建可能会导致 Out of Memory)

keepAliveTime(非核心线程结束后存活时间)、unit(时间单位)

0毫秒

workQueue(任务队列)

DelayedWorkQueue<Runnable>

迟滞任务队列

单参数方法:

android 定义全局线程池类库 java全局线程池_并发_21


可指定工厂的方法:

android 定义全局线程池类库 java全局线程池_java_22


测试周期定长线程池:

public static void main(String[] args) {
    //创建一个定长线程池,支持定时及周期性任务执行——延迟执行
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    //延迟1秒后每3秒执行一次
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        public void run() {
            System.out.println("延迟1秒后每3秒执行一次");
        }
    }, 1, 3, TimeUnit.SECONDS);
}

android 定义全局线程池类库 java全局线程池_并发_23

4、newSingleThreadExecutor() - 单任务线程池

创建一个单线程化的线程池,它只会用唯一的工作线程(串行)来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

参数

解释说明

corePoolSize(核心线程数)

1

maximumPoolSize(最大线程数)

1

keepAliveTime(非核心线程结束后存活时间)、unit(时间单位)

0毫秒

workQueue(任务队列)

LinkedBlockQueue<Runnable>

无界缓存任务队列

无参数方法:

android 定义全局线程池类库 java全局线程池_java_24


可指定工厂的方法:

android 定义全局线程池类库 java全局线程池_线程池_25


测试单任务线程池:

public static void main(String[] args) {
    //创建一个单线程化的线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        singleThreadExecutor.execute(new Runnable() {
            public void run() {
                try {
                    //结果依次输出,相当于顺序执行各个任务
                    System.out.println(Thread.currentThread().getName() + "正在被执行,打印的值是:" + index);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

android 定义全局线程池类库 java全局线程池_并发_26

5、newSingleThreadScheduledExecutor() - 周期性单任务线程池

创建一个单线程化的线程池,它只会用唯一的工作线程(串行)来执行任务,支持定时及周期性任务执行。

参数

解释说明

corePoolSize(核心线程数)

1

maximumPoolSize(最大线程数)

1

keepAliveTime(非核心线程结束后存活时间)、unit(时间单位)

0毫秒

workQueue(任务队列)

DelayedWorkQueue<Runnable>

迟滞任务队列

无参数方法:

android 定义全局线程池类库 java全局线程池_java_27

可指定工厂的方法:

android 定义全局线程池类库 java全局线程池_并发_28


周期性单任务线程池即定长为1的周期性定长线程池,这里我们不做测试。

五、合理的线程池大小

Java中获取CPU数量的代码:

Runtime.getRuntime().availableProcessors();

优化线程池大小所需要考虑的因素:

  • CPU数量
  • 内存大小
  • 避免极端情况

估算线程池大小公式:
 Ncpu = CPU数量
 Ucpu = 目标CPU的使用率(0 ≤ Ucpu ≤ 1)
 W/C = 等待时间 / 计算时间

最优线程池大小:
 Nthread = Ncpu × Ucpu × (1 + W/C)

六、分治:Fork / Join 框架

“分而治之” 一直是一个非常有效地处理大量数据的方法。

假设我们需处理1000个数据,但并不具备这种能力,我们可以只处理10个,再分阶段处理100次,最终合成即可得到想要的结果。

在 Linux 中,方法 fork() 用来创建子进程,使得系统进程可以多执行一个分支。Java线程也采取了类似的命名。

方法

作用

fork

开启线程

join

等待

我们不能毫无顾忌地使用 fork() 方法开启线程进行处理,可能会因开启过多的线程而严重影响性能。

※ ForkJoinPool线程池

在JDK中,给出了ForkJoinPool线程池,我们可以将线程提交给它进行处理,节省资源。

android 定义全局线程池类库 java全局线程池_任务队列_29


我们向ForkJoinPool 线程池提交 ForkJoinTask 任务,它支持 fork() 方法分解、 join() 方法等待的任务。ForkJoinTask为模板类,它实现了 Future接口,其下辖两个子类RecursiveTask(返回V类型)、RecursiveAction(无返回值),这三个类均为抽象类。无参数构造方法:

android 定义全局线程池类库 java全局线程池_android 定义全局线程池类库_30

全参数构造方法:

android 定义全局线程池类库 java全局线程池_任务队列_31


对于两种构造方法,无特殊需求,一般使用无参构造方法,全参数构造方法作为了解即可。

利用 Fork/Join 框架计算数列(等差数列:差值为1)求和:

public class CountTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 10000;     //任务不分割阈值
    private long start;           //起点
    private long end;             //终点

    /**
     * 构造方法初始化变量
     * @param start 起点
     * @param end   终点
     */
    public CountTask(long start, long end) {
        this.start = start;
        this.end = end;
    }
    
    /**
     * 重写 compute()方法
     * @return 返回子任务结果
     */
    @Override
    protected Long compute() {
        long sum = 0;           //记录总结果
        boolean canCompute = (end - start) < THRESHOLD;     //小于阈值可以直接运算,否则分割任务
        if (canCompute) {
            for (long i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            long step = (start + end) / 100;    //分割为100个子任务
            ArrayList<CountTask> subTasks = new ArrayList<>();  //存储各个子任务
            long pos = start;
            for (int i = 0; i < 100; i++) {
                long lastOne = pos + step;
                if (lastOne > end) lastOne = end;
                CountTask subTask = new CountTask(pos, lastOne);
                pos += step + 1;
                subTasks.add(subTask);  //添加子任务
                subTask.fork();         //执行子任务
            }
            for (CountTask task : subTasks) {
                sum += task.join();     //将子任务结果并入总结果
            }
        }
        return sum;     //返回总结果
    }
}

在main方法中创建对象并调用函数,计算0~300000等差数列的和:

//在main方法中创建对象并调用函数,计算0~300000等差数列的和
public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        CountTask task = new CountTask(0, 300000L);
        ForkJoinTask<Long> result = forkJoinPool.submit(task);
        try {
            long res = result.get();
            System.out.println("总和:" + res);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

android 定义全局线程池类库 java全局线程池_java_32

※ newWorkStealingPool()

newWorkStealingPool适合使用用于解决耗时操作的问题,newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,能够较为合理地分配CPU资源,它是一种具有抢占式操作的线程池。

//获取可用CPU数量
Runtime.getRuntime().availableProcessors();

以上代码可获取可用CPU数量,而全参数

无参数方法:

android 定义全局线程池类库 java全局线程池_任务队列_33


可指定并行数量的方法:

android 定义全局线程池类库 java全局线程池_任务队列_34