深入理解高并发编程:JDK核心技术|社区福利(第16期)-免费资源论坛-资源-SpringForAll社区

深入理解高并发编程:JDK核心技术|社区福利(第16期)

该帖子部分内容已隐藏
付费阅读
30积分
此内容为付费阅读,请付费后查看

在真实高并发场景下,一般不会直接使用 Thread 类创建线程,而是使用线程池来创建并管理线程。可以这么说,学好线程池对于并发编程是非常重要的。

01

线程池简介

线程池的创建和回收是一个非常消耗系统资源的过程,如果在系统中频繁地创建和回收线程,会极大降低程序的执行性能。并且,短时间内创建大量的线程可能造成 CPU 占用 100%、死机或内存溢出等问题。而使用线程池就能非常轻松地解决这些问题。

线程池核心类继承关系

线程池是 Java 从 JDK 1.5 版本开始提供的一种线程使用模式,能够自动创建和回收线程,并管理线程的生命周期。在线程池中能够管理和维护多个线程。

Java 的线程池主要是通过 Executor 框架实现的,涉及 Executor 接口、ExecutorServcie 接口、AbstractExecutorService 抽象类、ScheduledExecutorService 接口、ThreadPoolExecutor 类和ScheduledThreadPoolExecutor 类。线程池核心类继承关系如下图所示。

image

实现线程池最核心的类是ThreadPoolExecutor,而 ScheduledThreadPoolExecutor 类实现了定时任务功能,能够使提交到线程池中的任务定时、定期执行。为了便于创建线程池,除了上图所示的接口和类,JDK 还提供了一个 Executors 工具类,Executors 类中封装了创建线程池的各种方法,专门用于创建线程池。不过,在真实的高并发场景下,并不推荐使用 Executors 工具类创建线程池,而是推荐直接使用 ThreadPoolExecutor 类创建线程池。

02

线程池的优点

这里,综合对比直接使用 Thread 类创建线程的弊端与使用线程池的优点,来加深读者对线程池的理解。

1.直接使用 Thread 类创建线程的缺点

直接在程序中使用 Thread 类创建线程的方式是非常不可取的,主要体现在如下几方面。

(1)每次通过 Thread 类创建一个线程对象的性能是非常差的,每次创建 Thread 对象后,调用 Thread 的 start()方法都会在操作系统层面分配一个与之对应的线程,这个过程比较耗时。

(2)直接使用 Thread 类创建线程缺乏有效的统一管理机制,如果在短时间内创建大量线程,线程之间就会竞争系统资源,可能造成 CPU 占用 100%、死机或者内存溢出等问题。

(3)直接使用 Thread 类创建线程提供的线程功能非常有限,例如,无法让线程执行更多的任务、无法定期执行某些任务等。

(4)直接使用 Thread 类创建线程,无法对线程进行有效监控。

2.使用线程池管理线程的优点

使用线程池能够非常容易地解决直接使用 Thread 创建线程产生的问题,主要体现在如下几方面。

(1)线程池能够复用线程资源,有效减少了线程的创建和回收频率,减少了线程的创建与回收对系统性能造成的影响,比直接使用 Thread 类创建线程的系统性能高。

(2)使用线程池能够有效控制最大并发线程数,提高系统资源的利用率。创建的线程数是可控的,短时间内不会因为创建大量的线程导致线程过多地竞争资源,引起线程阻塞。

(3)在线程池中可以定时或定期执行某个或某些任务,提供了单线程执行任务的机制,也能够控制并发线程数。线程池提供了监控线程资源的方法,可以对线程池中的线程资源进行实时监控。

03

ThreadPoolExecutor 类

ThreadPoolExecutor 是线程池中最核心的类,通过查看 ThreadPoolExecutor 的代码可以得知,在使用 ThreadPoolExecutor 类的构造方法创建线程池时,最终会调用具有 7 个参数的构造方法,

代码如下。

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

接下来,对 ThreadPoolExecutor 类构造方法中每个参数的具体含义进行简单的介绍。

(1)corePoolSize 参数。表示线程池的核心线程数。

(2)maximumPoolSize 参数。表示线程池中的最大线程数。

(3)keepAliveTime 参数。表示线程没有任务执行状态保持的最长时间。当线程池中的线程数量大于 corePoolSize 时,如果没有新的任务提交,则核心线程外的线程不会立即销毁,需要等待,直到等待的时间超过 keepAliveTime 才会终止。

(4)unit 参数。表示 keepAliveTime 的时间单位。

(5)workQueue 参数。表示线程池中的阻塞队列,存储等待执行的任务。

(6)threadFactory 参数。线程工厂,用来创建线程池中的线程。提供一个默认的线程工厂来创建线程,当使用默认的线程工厂创建线程时,会为线程设置一个名称,使新创建的线程具有相同的优先级,并且是非守护线程。 

(7)rejectHandler 参数。表示拒绝处理任务时的策略。当 workQueue 阻塞队列已满、线程池中的线程数已经达到最大,且线程池中没有空闲线程时,如果继续提交任务,就需要采取一种策略来处理这个任务。

其中,在 ThreadPoolExecutor 类的构造方法中,最重要的 3 个参数是 corePoolSize、maximumPoolSize 和 workQueue,这 3 个参数会对线程池的运行过程产生重大的影响。

三者的关系如下

  • 如果线程池中运行的线程数小于 corePoolSize,则直接创建新线程处理任务,即使线程池中的其他线程是空闲的。

  • 如果运行的线程数大于或等于 corePoolSize 并且小于 maximumPoolSize,则只有当workQueue 队列满时,才会创建新的线程处理任务。如果 workQueue 队列不满,则将新提交的任务放入 workQueue 队列中。当设置的 corePoolSize 与 maximumPoolSize 相同时,创建的线程池大小是固定的,如果满足有新任务提交、线程池中没有空闲线程,且 workQueue 未满的条件,就把请求放入workQueue,等待空闲的线程从 workQueue 中取出任务进行处理。

  • 如果运行的线程数量大于 maximumPoolSize,同时 workQueue 已满,则通过拒绝策略参数 rejectHandler 来指定处理策略。

    线程池提供了 4 种拒绝策略,分别如下。

  • 直接抛出异常,这也是默认的策略。实现类为 AbortPolicy。

  • 使用调用者所在的线程来执行任务。实现类为 CallerRunsPolicy。

  • 丢弃队列中最靠前的任务并执行当前任务。实现类为 DiscardOldestPolicy。

  • 直接丢弃当前任务。实现类为 DiscardPolicy。

本文节选自深入理解高并发编程:JDK核心技术一书,本书是冰河编写的专注介绍JDK高并发编程技术的书籍。

关于书籍

JDK中有很多并发编程工具类,各种并发编程类库,比如:并发容器类、并发阻塞队列、并发非阻塞队列、并发工具类、锁工具类、无锁原子类、线程工具类和线程池等等,都是JDK中对于并发编程核心原理的深度实践。并且JDK中这些并发编程的类库经历了实际生产环境中高并发、大流量的考验,是学习高并发编程非常好的实践案例,并且这些案例是任何一个学习Java的小伙伴非常容易获得的宝贵资源。

尽管JDK中提供了很多并发编程的类库,但是,很多学习Java的小伙伴对于JDK中提供的并发编程类库的用法、原理和底层源码流程不太熟悉,这就导致很多小伙伴在学习并发编程时,处于浅尝辄耻的状态,了解一点并发编程的知识,但是不够系统和深入。平时了解的并发编程知识也达不到面试的要求,导致每次面试前都要重新背一遍八股文,收效甚微,最终浪费了很多宝贵的时间。更严重的是,在实际项目开发过程中,涉及到高并发编程时,还是一脸懵。

另外,市面上专注系统并深入介绍JDK并发编程的书籍和博客非常少,缺少JDK并发编程经验的小伙伴很难系统并深入的学习JDK并发编程知识,又会导致自身对于JDK并发编程知识的匮乏,最终又会导致进入面背被八股文的恶性循环中。

所以,《深入理解高并发编程:JDK核心技术》一书会系统并深入的介绍JDK并发编程的各种类库和线程池,让你从案例、原理和底层源码执行流程等方面彻底理解JDK并发编程技术,告别面试背八股文的恶性循环,提升并发编程的内功修炼,为你的面试和职业生涯保驾护航。

抽奖赠书

本次福利将送出深入理解高并发编程:JDK核心技术* 5本

开奖时间:2023年4月12日 9:00

抽奖方式

第一步:关注社区公众号:SpringForAll

第二步:发送抽奖口令

请登录后发表评论