【多线程】基础 | 你到底懂不懂线程池?

3,740 阅读4分钟

概要

1. 什么是线程池
2. 为什么要使用线程池,主要是为了解决什么问题,线程池的好处?
3. 通过源码看实现
4. 线程池的优化

1. 什么是线程池?

线程池(Thread Pool)是一种基于池化思想管理线程的工具,预先创建一些对象放入池中,使用的时候直接取出使用,用完归还接下去复用,通过一定的策略调整 池中参数,实现池的动态伸缩。线程会带来许多的额外的问题,创建调度线程都会有额外的开销,利用线程池维护多个线程, 既可以减少线程重复的创建与销毁,同时可以避免线程锁膨胀导致过分调度的问题,充分利用内核。

2.1 为什么要使用线程池?

  1. 降低资源消耗
  2. 提高响应速度,任务到达时可以立即执行,核心线程满时,可以进入等待,当可以进行执行的时候,马上进行任务执行。
  3. 提高对线程的管理,利用线程池可以统一进行调度、监控和调优。
  4. 提供更多的功能

2.2 为了解决什么问题?

资源管理问题,为的是减少额外的资源消耗,根据所处服务器的CPU创建线程池,减少资源耗尽的风险提高稳定性。

2.3 线程池的好处

  1. 降低资源消耗。通过复用已存在的线程和降低线程关闭的次数来尽可能降低系统性能损耗;
  2. 提升系统响应速度。通过复用线程,省去创建线程的过程,因此整体上提升了系统的响应速度;
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,因此,4. 需要使用线程池来管理线程。

2.4 如何合理配置

维度 任务的性质:CPU 密集型任务(执行时间长,配置Ncpu+1个线程的线程池),IO 密集型任务(执行速度快,2xNcpu)和混合型任务。

任务的优先级:高,中和低。

任务的执行时间:长,中和短。

任务的依赖性:是否依赖其他系统资源,如数据库连接。

3.接下来查看的代码是基于JDK1.8的源码进行分析。

  1. 看东西我们先看一个整体,通过ThreadPoolExecutor的UML图,看一下整体的结构。
Executor:提供execute方法,将任务提交和任务执行解耦,不需要使用new Thread这种方式启动任务。
ExecutorService:提供方法进行任务的中断。
//启动一个有序关闭
void shutdown();
//是否中断
isShutdown();
//阻塞,直到所有请求完成后执行
awaitTermination(long timeout, TimeUnit unit);
//提交,用get() 获取结果
submit(Callable<T> task)
//执行给定任务,任务执行完成时返回结果列表
List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

AbstractExecutorService:上层的抽象类,提供默认实现ExecutorService方法。 ThreadPoolExecutor的运行机制: 图片来源:美团文章

  1. 生命周期状态 主线程状态控制
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// runState 出在高阶位中COUNT_BITS = Integer.SIZE - 3;
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;

// Packing and unpacking ctl
//计算当前运行状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//计算当前线程数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }

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

先来看一ThreadPoolExecutor的主要构件函数

// 五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

// 六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

// 六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)

// 七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

参数含义

任务调度机制

public void execute(Runnable command) {
        //判断传入的线程是否为空,如果为空则抛出空指针异常
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //情况1,当前线程数<核心线程数
        if (workerCountOf(c) < corePoolSize) {
            //开启一个新的线程,来提交任务,这一步需要获取全局锁
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //情况2,检查当前线程是否运行,并向队列中添加任务
        if (isRunning(c) && workQueue.offer(command)) {
            //再次检查,万一线程池处于非Running
            int recheck = ctl.get();
            //再次检测线程池是否处于运行状态,如果否,则移除并回调拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //当workerCountOf为0,则添加一个线程用于执行提交的任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //当线程池已经饱和,回调拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

4. 线程池的优化(笔记)

1. Java内部锁优化

2. 代码中锁的优化

总结

本篇文章主要是对线程有一个大概的了解,知道线程池的优劣以及具体的实现,接下去我们主要围绕拒绝策略、线程池的用法,以及他在工作中所遇到的坑,进行一些扩展。

文章有不足的地方,希望大家能够指出,欢迎评论~