写点什么

刨根问底!阿里 p7 大佬带你深入源码,玩转 Java 线程池原理,快收藏

发布于: 2021 年 06 月 11 日
刨根问底!阿里p7大佬带你深入源码,玩转Java线程池原理,快收藏

今日分享开始啦,请大家多多指教~

线程池(Executor)

什么是线程池?

Java5 引入了新的称为 Executor 框架的并发 API,以简化程序员的工作。它简化了多线程应用程序的设计和开发。它主要由 Executor、ExecutorService 接口和 ThreadPoolExecutor 类组成,ThreadPoolExecutor 类同时实现 Executor 和 ExecutorService 接口。ThreadPoolExecutor 类提供线程池的实现。我们将在教程的后面部分了解更多。

线程池继承关系图

为什么我们需要线程池?

当我们创建一个简单的多线程应用程序时,我们创建 Runnable 对象,并使用 Runnable 构造线程对象,我们需要创建、执行和管理线程。我们可能很难做到这一点。Executor 框架为您做这件事。它负责创建、执行和管理线程,不仅如此,它还提高了应用程序的性能。

当您为每个任务创建一个新线程,然后如果系统高度过载,您将出现内存不足错误,系统将失败,甚至抛出 oom 异常。如果使用 ThreadPoolExecutor,则不会为新任务创建线程。将任务分配给有限数量的线程只去执行 Runnable,一旦线程完成一个任务,他将会去阻塞队列中获取 Runnable 去执行。

如何创建线程池?

还有另一个名为 ExecutorService 的接口,它扩展了 Executor 接口。它可以被称为 Executor,它提供了可以控制终止的方法和可以生成未来跟踪一个或多个异步任务进度的方法。它有提交、关机、立即关机等方法。

ThreadPoolExecutor 是 ThreadPool 的实际实现。它扩展了实现 ExecutorService 接口的 AbstractThreadPoolExecutor。可以从 Executor 类的工厂方法创建 ThreadPoolExecutor。建议使用一种方法获取 ThreadPoolExecutor 的实例。

  • 使用 Executors 工厂方法去创建线程池:

提供默认静态方法

Executors 类中有 4 个工厂方法可用于获取 ThreadPoolExecutor 的实例。我们正在使用 Executors 的 newFixedThreadPool 获取 ThreadPoolExecutor 的一个实例。

ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);

  • 自定义 ThreadPoolExecutor 的创建线程池

提供默认构造函数

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,

TimeUnit unit,BlockingQueue workQueue ,ThreadFactory threadFactory,RejectedExecutionHandler handler) ;

ThreadPoolExecutor 源码分析

  • 线程池内部状态

ctl 变量利用低 29 位表示线程池中线程数,通过高 3 位表示线程池的运行状态:

  • RUNNING:-1 << COUNT_BITS,即高 3 位为 111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;

  • SHUTDOWN: 0 << COUNT_BITS,即高 3 位为 000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;

  • STOP : 1 << COUNT_BITS,即高 3 位为 001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;

  • TIDYING : 2 << COUNT_BITS,即高 3 位为 010, 所有的任务都已经终止;

  • TERMINATED: 3 << COUNT_BITS,即高 3 位为 011, terminated。

状态转换图

下面带大家分析下 ThreadPoolExecutor 内部几个核心方法:

  • 添加任务:execute(Runnable command)

执行 Runnable 入口方法


添加任务流程图

  • 添加工作队列 addWorker(Runnable firstTask, boolean core)

我们接下来看看如何添加 worker 线程的




  • 执行任务: runWorker(Worker w)

在 addWorker 成功后会调用 Worker 的 start()方法,接下来来分析下如何执行任务的。


看到这里我们还没看到当 worker 线程数>coreSize 时候是如何去回收线程的,不用着急,接下来我们去看下 getTask()方法。

  • 获取 task 任务: getTask()


  • 关闭线程: shutdown()

  • 立即关闭线程: shutdownNow()

此方法会中断任务执行,返回未执行的 task

线程池使用注意事项

  • 使用 ThreadLocal

ThreadLocal 称为线程本地存储,一般作为静态域使用,它为每一个使用它的线程提供一个其值(value)的副本。通常对数据库连接(Connection)和事务(Transaction)使用线程本地存储。 可以简单地将 ThreadLocal 理解成一个容器,它将 value 对象存储在 Map<Thread, T> 域中,即使用当前线程为 key 的一个 Map,ThreadLocal 的 get() 方法从 Map 里取与当前线程相关联的 value 对象。ThreadLocal 的真正实现并不是这样的,但是可以简单地这样理解。线程池中的线程在任务执行完成后会被复用,所以在线程执行完成时,要对 ThreadLocal 进行清理(清除掉与本线程相关联的 value 对象)。不然,被复用的线程去执行新的任务时会使用被上一个线程操作过的 value 对象,从而产生不符合预期的结果。

  • 设置合理的线程数

新手可能对使用线程池有一个误区,并发越高使用更多线程数,然而实际的情况就是过多的线程会造成系统大量的 Context-Switch 从而影响系统的吞吐量,所以合理的线程数需要结合项目进行压测,一般我们主要针对 2 种类型的任务设置线程数规则为:

1.cpu 密集型

coreSize == cpu 核心数+1

2.Io 密集型

coreSize == 2*cpu 核心数

今日份分享已结束,请大家多多包涵和指点!

用户头像

还未添加个人签名 2021.04.20 加入

Java工具与相关资料获取等WX: pfx950924(备注来源)

评论

发布
暂无评论
刨根问底!阿里p7大佬带你深入源码,玩转Java线程池原理,快收藏