10 分钟学会使用 Java 多线程

news/2024/7/8 7:09:14

大家好,我是伍六七。

今天阿七来聊聊 Java 程序员们面试、工作中经常会碰到的线程池。它的概念、原理、使用以及可能会碰到的一个坑。

一、Java 线程池基本概念

1、线程池的 7 个核心参数

这是 Java 初中级程序员们面试必问的面试题了,我们来看:

  • corePoolSize(核心线程数)

corePoolSize 是线程池中保持活动状态的最小线程数。
即使线程是空闲的,它们也会一直保持在池中。
当有新任务提交时,线程池会优先创建核心线程来处理任务。

  • maximumPoolSize(最大线程数)

maximumPoolSize 是线程池中允许的最大线程数。
如果任务数超过了核心线程数,且任务队列已满,线程池会创建新的线程,但不会超过最大线程数。

  • keepAliveTime(线程空闲时间)

keepAliveTime 是非核心线程在空闲时可以存活的时间。
当线程空闲时间超过 keepAliveTime,多余的非核心线程将被终止,以减少资源消耗。

这个参数配合 TimeUnit 来定义时间单位。

  • unit(时间单位):

unit 是与 keepAliveTime 一起使用的时间单位。
它表示 keepAliveTime 的时间单位,可以是秒、毫秒、微秒等。

  • workQueue(任务队列):
    workQueue 是一个阻塞队列,用于存储等待执行的任务。
    当任务数超过核心线程数时,多余的任务会被放入任务队列中。
    常见的队列类型包括 LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue 等。

  • threadFactory(线程工厂):

threadFactory 用于创建新线程。
可以通过自定义线程工厂来配置线程的名称、优先级、是否为守护线程等属性。

  • handler(饱和策略)

handler 是当工作队列和线程池都满了之后采取的饱和策略。
常见的饱和策略有 AbortPolicy(默认,抛出异常)、CallerRunsPolicy(由调用线程执行任务)等。

这些参数在创建线程池时进行配置,通过合理调整这些参数,可以使线程池适应不同的工作负载和性能需求。例如,通过调整核心线程数和最大线程数,可以优化线程池在不同负载下的性能表现。

2、线程池是怎么运转的?

举例来说:核心线程数量为 5 个;全部线程数量为 10 个;工作队列的长度为 5。

刚开始都是在创建新的线程,达到核心线程数量 5 个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列;

任务队列到达上线 5 个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量 10 个;

后面的任务则根据配置的饱和策略来处理。如果没有配置,使用的是默认的配置 AbortPolicy:直接抛出异常。

当当前任务小于最大线程数的时候,线程资源会保持核心线程池个数的线程,其他超过的线程资源在存活时间时间之后会被回收。

二、Future 关键字

我们在项目中会经常使用 CompletableFuture 执行异步任务。那你知道 CompletableFuture 使用的是什么线程池吗?这个线程池适合执行什么类型的任务呢?

之前阿七刚转到互联网的时候,就因为使用 CompletableFuture 不当,被领导 diss 了。

我们看看源码:

// 是否使用 useCommonPool,如果(cpu 的核数 -1)大于 1,使用 ForkJoinPool,否则,不使用线程池。
    private static final boolean useCommonPool =
            (ForkJoinPool.getCommonPoolParallelism() > 1);
/**
     * Default executor -- ForkJoinPool.commonPool() unless it cannot
     * support parallelism.
     */
    // 使用线程池还是创建普通线程
    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
/** Fallback if ForkJoinPool.commonPool() cannot support parallelism */
    static final class ThreadPerTaskExecutor implements Executor {
        public void execute(Runnable r) { new Thread(r).start(); }
    }

我们看到,默认情况下 CompletableFuture 会使用公共的 ForkJoinPool 线程池,这个线程池默认创建的线程数是 CPU 的核数

PS:也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数。

但是也不一定就使用 ForkJoinPool,要看(cpu 的核数 -1)是否大于 1,如果大于 1,使用过 ForkJoinPool,否则,创建普通线程执行。

cpu 核数 = Runtime.getRuntime().availableProcessors();

我们要知道 CompletableFuture 获取返回是阻塞的,那我们在执行 IO 操作的时候,如果我们直接使用默认的线程池,有很大概率是会阻塞其他操作的。

所以,我们使用 CompletableFuture 的时候,如果执行 CPU 操作,可以使用默认线程池。

但是,如果执行的是 IO 操作,比如 DB 增删改查、接口调用等,尽量使用自定义线程池。

三、自定义线程池

有些情况,我们需要做到资源隔离,比如上面使用 进行 IO 操作,我们需要自定义线程池,那我们怎么定义呢?

3.1 ThreadPoolExecutor

我们可以使用 ThreadPoolExecutor,指定核心参数进行线程吹创建。

ThreadPoolExecutor cutomerPoolExecutor = new ThreadPoolExecutor(10,
                10,
                1,
                TimeUnit.DAYS,
                new ArrayBlockingQueue<>(1000),
                new NamedThreadFactory("business-operation-"));

创建好之后,我们就可以往里面放任务了!我们来看个例子:
首先,创建一个任务:

// 测试任务,sleep 1s,模拟执行耗时任务
public class TestTask implements Runnable {
@Override
    public void run() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

把任务放到线程池中,直接 submit 即可。

personalPoolExecutor.submit(new TestTask());
3.2 怎么设置线程池的参数?

线程池究竟设成多大是要看你给线程池处理什么样的任务,任务类型不同,线程池大小的设置方式也是不同的。

任务一般可分为:CPU 密集型、IO 密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。

  • CPU 密集型任务

尽量使用较小的线程池,一般为 CPU 核心数 +1。
因为 CPU 密集型任务使得 CPU 使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。

  • IO 密集型任务

可以使用稍大的线程池,一般为 2*CPU 核心数。
IO 密集型任务 CPU 使用率并不高,因此可以让 CPU 在等待 IO 的时候去处理别的任务,充分利用 CPU 时间。

  • 混合型任务

最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]
可以将任务分成 IO 密集型和 CPU 密集型任务,然后分别用不同的线程池去处理。

只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。

因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

你学会了嘛?学会了点个赞再走~


关注我,送你全套我整理的 Java 岗位面试资料。这是我自己之前整理的面试题,靠着这份面试题,我从 30 人的小公司,进了 2000 人+的央企子公司,之后又进了互联网大厂。

一份让我进大厂&央企的面试题


http://lihuaxi.xjx100.cn/news/1788283.html

相关文章

【技术干货】开源库 Com.Gitusme.Net.Extensiones.Core 的使用(二)

Com.Gitusme.Net.Extensiones.Core 扩展库 1.0.6 版本已发布。 1、版本变更说明 新增Sokcet套接字扩展。简化Socket操作&#xff0c;支持自定义命令封装&#xff0c;使用方便快捷&#xff0c;让您聚焦业务实现&#xff0c;而不必关心底层逻辑&#xff0c;提高开发效率。日志功…

电脑远程监控软件大揭秘

电脑远程监控软件是一种通过互联网远程控制另一台电脑的软件&#xff0c;通常需要安装在被监控的电脑上&#xff0c;并由控制者通过电脑或手机进行远程操作。 这种软件广泛应用于企业、学校、家庭等场景&#xff0c;可以帮助管理者监控员工或孩子的电脑使用情况&#xff0c;保护…

【汇编】计算机的组成

文章目录 前言一、计算机的基本组成1.1 中央处理器&#xff08;CPU&#xff09;1.2 内存指令和数据存储的位置计算机中的存储单元计算机中的总线地址总线数据总线控制总线 1.3 输入设备和输出设备1.4 存储设备 二、计算机工作原理三、计算机的层次结构总结 前言 计算机是现代社…

股市助手:实时股市快讯,真人语音播报,助您第一时间获取最新资讯(自己写的分享给需要的人)

文章目录 &#x1f4d6; 介绍 &#x1f4d6;&#x1f3e1; 使用环境 &#x1f3e1;&#x1f4d2; 使用方法 &#x1f4d2;&#x1f4dd; 软件设置&#x1f4dd; 软件运行 &#x1f4d6; 介绍 &#x1f4d6; 给大家分享一款自己写的软件《股市助手》&#xff0c;老规矩&#xff…

js 处理货币信息转换

// 货币格式化 export function formatCurrency(num) {if (!num) return "0.00";num num.toString().replace(/\$|\,/g, "");if (isNaN(num)) num "0";let sign num (num Math.abs(num));num Math.floor(num * 100 0.50000000001);let ce…

2023湖南省赛

​​​​​​连接 目录 A:开开心心233 B:Square Game F:necklace K:tourist 补题中&#xff0c;会给出大部分代码 A:开开心心233 签到题 &#xff0c;无论二分还是解方程还是直接for循环枚举都能直接通过啦 signed main() {ios_base::sync_with_stdio(0); cin.tie(0),co…

【Android】统一系统动画

需求&#xff1a;除panel动画效果为弹出之外&#xff0c;其余的应用效果为渐入渐出 从系统层面统一把控动画效果&#xff0c;而不是单个应用自己处理 Android系统版本&#xff1a;9.0 代码地址 \frameworks\base\core\res\res\values\styles.xml 当时看注释&#xff0c;以为…

BUUCTF 爱因斯坦 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 下载附件&#xff0c;解压得到一张.jpg图片。 密文&#xff1a; 解题思路&#xff1a; 1、因为题目没有什么提示&#xff0c;我们就一一尝试。将图片放到StegSolve中&#xff0c;在查看图片的File Format时&#x…