JUC 线程池的原理与实战&各个参数解析

102 阅读3分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

引言

Java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作:

(1)必须为线程堆栈分配和初始化大量内存块,其中包含至少1MB的栈内存。

(2)需要进行系统调用,以便在OS(操作系统)中创建和注册本地线程。

Java高并发应用频繁创建和销毁线程的操作是非常低效的,而且是不被编程规范所允许的。如何降低Java线程的创建成本?必须使用到线程池。

是什么?线程池是什么?有什么功能?

线程池主要解决了以下两个问题:

(1)提升性能:线程池能独立负责线程的创建、维护和分配。在执行大量异步任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大限度地对已经创建的线程进行复用,使得性能提升明显。

(2)线程管理:每个Java线程池会保持一些基本的线程统计信息,例如完成的任务数量、空闲时间等,以便对线程进行有效管理,使得能对所接收到的异步任务进行高效调度。

为什么要用线程池?

在主要大厂的编程规范中,不允许在应用中自行显式地创建线程,线程必须通过线程池提供。由于创建和销毁线程需要时间以及系统资源开销,使用线程池的好处是减少这些开销,解决资源不足的问题。

如何使用线程池?线程池的各个参数详解

使用new ThreadPoolExec()方法创建线程池有四个重载方法:

image.png 最全的一共有7个参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

我们从上往下挨个说

1. 核心线程数量

当程序通过execute()/submit()方法提交提交任务时,如果线程池中的有效线程数低于核心线程数,线程池会创建新线程来处理这个任务,直到有效线程数等于核心线程数。 如果此时任务继续提交,不会继续创建线程,而是把任务放到队列中排队。

2. 最大线程数

当排队的队列满了之后,线程池又开始创建新线程,直到活动的线程数等于最大线程数。

3和4. 非核心线程存活时间与时间单位

当线程们开足火力处理完任务后,超过核心线程数量的线程将被销毁,销毁的时间是由存活时间和存活时间单位来确定。

(todo 这一步是如何实现的呢?)

5. 任务队列

线程队列既是我们刚才所说的存放任务的地方。

6. 线程工厂

线程工厂一听就知道是使用了工厂模式的思想,有一个专门的类来创建线程。 默认是使用defaultThreadFactory,当然也可以自定义线程工厂,通过实现ThreadFactory,重写她的newThread()方法,在这里我们可以定义创建出的线程的名称,线程组,优先级和是否守护线程四个参数。

7. 拒绝策略

当我们的工作线程开到最多并且任务队列也被塞满后,这时再提交的线程就会被拒绝,默认的拒绝策略是AbortReject抛出异常,另外还有3种可选的拒绝策略分别是:静默型拒绝,不抛出异常,直接丢弃;丢弃最老的任务;让提交者执行。

实战

说了这么多我们来写一个线程池吧