查看原文
其他

掌握这两本书:搞定高并发这块硬骨头|文末赠书

程序猿DD 2023-05-18

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

01

线程池简介

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

线程池核心类继承关系

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

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

实现线程池最核心的类是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核心技术》一书从实际需求出发,将全书分为三个大的篇章,分别是:JDK高并发编程的基础知识、核心工具和线程池核心技术。


第一篇 基篇(第1~2章)

本篇简单地介绍了进程与线程的基本概念、线程调度与上下文切换、进程与线程的综合对比、如何查看进程与线程的运行时信息,以及线程和线程组的基本操作。

第二篇 核心工具篇(第3~13章)

本篇通过大量源码和案例详细介绍了JDK的各种并发工具,涵盖同步集合、并发List集合类、并发Set集合类、并发Map集合类、并发阻塞队列、并发非阻塞队列、并发工具类、锁工具类、无锁原子类、线程工具类和异步编程工具类。几乎每个章节都配有JDK核心工具类的源码及实战案例,有助于读者理解。

第三篇 线程池核心技术篇(第14~16章)

本篇深入剖析了JDK中线程池的核心源码。包括线程池顶层接口和抽象类、线程池正确运行的核心流程、线程池执行任务的核心流程、Worker线程的核心流程、线程池优雅退出的核心流程、ScheduledThreadPoolExecutor类与Timer类的区别、定时任务线程池的初始化、调度流程和优雅关闭流程等。通过对本篇的学习,读者能够从源码级别深刻理解线程池的核心原理和执行流程。

为了进一步加深读者对线程池的理解,在本篇的随书源码中,会给出完整的手写线程池的案例程序。




本书特色

1. 系统介绍JDK高并发编程的图书

目前,图书市场少有全面细致地介绍有关JDK高并发编程的基础知识、核心工具和线程池核心技术的图书。

本书从以上三方面入手,全面、细致并且层层递进地介绍了JDK高并发编程相关知识。

2. 大量图解和开发案例

为了便于理解,笔者在介绍JDK高并发编程的基础知识和核心工具章节中会配有适量的图解和图表,以及对应的实战案例。

在线程池核心技术章节中会配有完整的手写线程池案例。读者按照本书的案例学习,并运行案例代码,能够更加深入地理解和掌握相关知识。

另外,这些案例代码和图解的draw.io原文件会一起收录于随书资料里。读者也可以访问下面的链接,获取完整的实战案例源码和相关的随书资料。

  • GitHub:https://github.com/binghe001/mykit-concurrent-jdk

  • Gitee:https://gitee.com/binghe001/mykit-concurrent-jdk

  • GitCode:https://gitcode.net/binghe001/mykit-concurrent-jdk

3. 技术点与案例结合

对于JDK高并发编程的各项技术,书中都配有相关的典型案例,具有很强的实用性,方便读者随时查阅和参考。

4. 具备较高的实用价值

本书中大量的实战案例来源于笔者实际的工作总结,尤其是核心工具篇与线程池核心技术篇涉及的内容,具有非常高的参考与实用价值。

其中,在线程池核心技术篇的随书源码中,会带着大家从零开始手写线程池核心源码。


冰河的第一本《深入理解高并发编程:核心原理与案例实战》,让你彻底理解并发编程产生各种诡异Bug问题的根源,掌握问题的根本解决方案,并深度剖析并发编程的底层核心原理和企业级架构实战

冰河的第二本《深入理解高并发编程:JDK核心技术》,让你从源码级别彻底吃透JDK中的各种并发类库和线程池,并带你实战手写一款属于自己的线程池。

一本深度剖析并发编程底层原理,一本深度解密JDK对于并发原理的实现,两本相辅相成,让你既懂原理,又懂实现,总之,一句话:要知其然,更要知其所以然。掌握这两本书的核心知识,应该在并发领域能超越大部分人吧?

抽奖赠书

本次福利将送出《深入理解高并发编程:JDK核心技术》* 5本,超高中奖率(参与人少,多期中奖率超过90%)
为避免撸羊毛的用户参与抽奖,让更多真实用户获得社区福利,现在把抽奖方式切换到 spring4all.com 网上通过积分参与。
本次抽奖地址:http://spring4all.com/forum-post/2213.html
还有一大波福利正在路上,一起来参与社区内容的建设,一起学习一起成长吧!
如何获取积分?日常登录spring4all.com,发帖交流、分享资源、回帖帮助他人等有益内容的,均可获得积分!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存