厉害了,线程池就该这么玩

简介: 厉害了,线程池就该这么玩

预计阅读 7分钟建议收藏后阅读,转发分享是前进的动力,原创不易

在 Java 语言中创建线程有两种方式,分别是实现 Runnable 接口或者 new Thread()就可以了,但是实际上创建线程可不仅是创建对象这么简单。创建对象仅仅是在 JVM 的堆分配一块内存而已;

而创建线程还需要和操作系统内核的 API,然后操作系统还要为线程分配一系列资源。涉及内核切换,这个成本就很高。所以线程是一个重量级对象,要避免频繁的创建和销毁。

所以问题来了,如何避免呢?那就是利用池化技术思想,线程池登场。

1. 快速创建线程池

线程池的需求是如此普遍,所以 Java SDK 并发包自然也少不了它。Java 并发包里提供了一个线程池的静态工厂类 Executors,利用Executors你可以快速创建线程池。不过目前大厂的编码规范中基本上都不建议使用Executors了 。

不建议使用的重要原因是:提供的很多方法默认使用的是无界的 LinkedBlockingQueue, 高负载情况下,无界队列很容易导致 OOM,一旦 OOM 会导致所有请求都无法处理。

主要提供的工厂方法如下所示:

  1. newCachedThreadPool() 创建一个可缓存的线程池,调用 execute() 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
  2. newFiexedThreadPool(int Threads):创建一个固定数量线程的线程池,并使用 LinkedBlockingQueue无界队列保存任务。
  3. newSingleThreadExecutor():创建一个单线程化的 Executor
  4. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代 Timer 类。
    虽然这些方法不推荐使用 ,但是我们还是要了解为何不用,主要还是不能很好的去控制线程池的策略。以及队列类型。所以接下来我们介绍重要的 ThreadPoolExecutor来创建线程池。

2. ThreadPoolExecutor 合理创建线程池

ThreadPoolExecutor构造函数非常复杂,如下所示,这个最完备的构造函数有 7 个参数。

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

接下来我们一一介绍每个参数的含义。我们可以把线程池比拟为 一个项目组,线程就是项目组的成员

  1. corePoolSize:表示线程池保有的最小线程数。即使很闲也不能把人给撤了,至少要留 corePoolSize 个人坚守。
  2. maximumPoolSize:线程池创建的最大线程数。当项目很忙的时候,需要加人但是并不是无限制的加,最多加到 maximumPoolSize 个人,当项目闲下来的时候,就要撤走,最多能撤到 corePoolSize 个人。
  3. keepAliveTime & unit:刚提到根据项目闲忙来增减人员,在 Java 这如何定义忙闲?就是通过一个线程如果在一段时间内都没有执行任务,说明很闲了。keepAliveTime & unit 就是用来定义这个一段时间的参数。如果线程数大于 corePoolSize 且空闲了keepAliveTime & unit这么久,那么这个空闲的线程就要被回收了。
  4. workQueue:工作队列,当前忙碌的线程数量达到了 corePoolSize且任务队列还没满的时候,新的任务会先被放到该队列中。
  5. threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
  6. handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。ThreadPoolExecutor已经提供了以下 4 种策略。也可自定义拒绝策略,只要实现 RejectedExecutionHandler
    Java 在 1.6 版本还增加了allowCoreThreadTimeOut(boolean value) 方法,它可以让所有线程都支持超时,这意味着如果项目很闲,就会将项目组的成员都撤走。
  • CallerRunsPolicy:提交任务的线程自己去执行该任务。
  • AbortPolicy:默认的拒绝策略,会throws RejectedExecutionException
  • DiscardPolicy:丢弃最老的任务,也就是把最早进入工作队列的任务丢掉,然后把新任务加到工作队列中。

3. 代码实战

public class ThreadPoolExecutorDemo {
    // 创建一个线程池
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 8
            , 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), new NamedThreadFactory("my exec pool-", false),
            new CustomizeDiscardPolicy());
    public void execute(Runnable runnable) {
        threadPoolExecutor.execute(runnable);
    }
    /**
     * 自定义拒绝策略
     */
    public static class CustomizeDiscardPolicy implements RejectedExecutionHandler {
        public CustomizeDiscardPolicy() {
        }
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("Task " + r.toString() + " rejected from " + executor.toString());
        }
    }
}
public class ThreadPoolExecutorTest {
    @Test
    public void testRun() {
        ThreadPoolExecutorDemo threadPoolExecutorDemo = new ThreadPoolExecutorDemo();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            threadPoolExecutorDemo.execute(() -> System.out.println(Thread.currentThread().getName() + finalI));
        }
    }
}

4.总结

线程池就是一种生产者-消费者模式, 线程池的使用方是生产者,线程池本身是消费者。

至于设置多大的线程数量:

1. 对于 CPU 密集型的计算场景,理论上“线程的数量 =CPU 核数”就是最合适的。不过在工程上,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。

最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]。除此之外,其实 spring 也帮我们封装了线程池的创建,读者们可以查看 Spring Boot 实现异步调用


相关文章
|
消息中间件 NoSQL Java
别再用 Redis List 实现消息队列了,Stream 专为队列而生
别再用 Redis List 实现消息队列了,Stream 专为队列而生
300 0
|
测试技术 开发者 Python
自动化测试之美:从零构建你的软件质量防线
【10月更文挑战第34天】在数字化时代的浪潮中,软件成为我们生活和工作不可或缺的一部分。然而,随着软件复杂性的增加,如何保证其质量和稳定性成为开发者面临的一大挑战。自动化测试,作为现代软件开发过程中的关键实践,不仅提高了测试效率,还确保了软件产品的质量。本文将深入浅出地介绍自动化测试的概念、重要性以及实施步骤,带领读者从零基础开始,一步步构建起属于自己的软件质量防线。通过具体实例,我们将探索如何有效地设计和执行自动化测试脚本,最终实现软件开发流程的优化和产品质量的提升。无论你是软件开发新手,还是希望提高项目质量的资深开发者,这篇文章都将为你提供宝贵的指导和启示。
|
12月前
|
关系型数据库 MySQL Linux
MySQL版本升级(8.0.31->8.0.37)
本次升级将MySQL从8.0.31升级到8.0.37,采用就地升级方式。具体步骤包括:停止MySQL服务、备份数据目录、下载并解压新版本的RPM包,使用`yum update`命令更新已安装的MySQL组件,最后启动MySQL服务并验证版本。整个过程需确保所有相关RPM包一同升级,避免部分包遗漏导致的问题。官方文档提供了详细指导,确保升级顺利进行。
1247 16
|
8月前
|
人工智能 自然语言处理 测试技术
谷歌AI 多模态 Gemini 2.5 Pro的国内使用教程
在人工智能(AI)的星辰大海中,谷歌再次投下一枚重磅炸弹 💣!他们倾注心血打造的智慧结晶
3525 0
|
Kubernetes 监控 网络协议
在K8S中,Pod有几种探针?
在K8S中,Pod有几种探针?
|
开发工具 git
git clone如何拉取代码,抓取和拉取
git clone如何拉取代码,抓取和拉取
|
12月前
|
视频直播 UED
体育动画直播,观赛的新潮流
体育动画直播利用动画技术和实时数据,生动呈现比赛进程,增强观众参与感。篮球、足球及电竞赛事中,通过动画展示球员轨迹和比赛数据,使观众更直观了解比赛进展。熊猫比分推出的最新版体育动画直播产品,界面可高度定制,支持动画UI和品牌LOGO自定义,云传输技术确保比赛进度领先视频直播,极大提升用户体验。
|
监控 Linux
Zabbix 5.0 LTS的agent服务部署实战篇
文章介绍了如何在CentOS 7.6操作系统上部署Zabbix 5.0 LTS版本的agent服务,包括配置软件源、安装agent、修改配置文件、启动服务,并在Zabbix web界面添加监控。
478 4
Zabbix 5.0 LTS的agent服务部署实战篇
|
Java Android开发
Android零基础入门(二)gradle的安装和详解
Android零基础入门(二)gradle的安装和详解
269 0
|
存储 NoSQL 网络协议
Redis Cluster 原理说的头头是道,这些配置不懂就是纸上谈兵
Redis Cluster 原理说的头头是道,这些配置不懂就是纸上谈兵
198 0