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

508 阅读3分钟

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

线程池(Executor)

什么是线程池?

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

image.png

为什么我们需要线程池?

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

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

如何创建线程池?

image.png

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

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

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

提供默认静态方法

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

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

image.png

  • 自定义ThreadPoolExecutor的创建线程池

提供默认构造函数

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

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

image.png

ThreadPoolExecutor源码分析

  • 线程池内部状态

image.png

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。

image.png

状态转换图

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

- 添加任务:execute(Runnable command)

执行Runnable入口方法

image.png

image.png

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

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

image.png

image.png

image.png

image.png

  • 执行任务: runWorker(Worker w)

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

image.png

image.png

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

- 获取task任务: getTask()

image.png

image.png

- 关闭线程: shutdown()

image.png

- 立即关闭线程: shutdownNow()

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

image.png

线程池使用注意事项

- 使用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核心数

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