java多线程:线程池入门详解

在开始这篇文章之前,首先提出几个问题:

  • 什么是线程池?
  • 为什么要用线程池?
  • 线程池的7个核心参数及简单使用
  • 线程池的执行流程?
  • 进阶:线程池是怎么区别核心线程和非核心线程的?

什么是线程池

线程池,即持有管理线程的工具,能接收用户的任务并分配给其中一个线程异步执行。

为什么要用线程池

new Thread()执行的缺点

使用传统的new Thread方法,手动地执行start 运行后,则新建了一个线程执行指定的任务,而线程不能复用,在执行完成之后就自动地销毁了。之后再执行时,又要重新创建线程,这样造成了线程的创建和销毁的性能损耗。同时,手动创建线程通常不会考虑系统负载情况,当任务某个点激增时,无限制地新建线程可能导致系统宕机。
总结: 1、线程不能复用,创建、销毁损耗性能 2、不能对线程进行管理

使用线程池的优点

线程池持有管理了线程(核心和非核心线程)。当有任务需要执行时,由线程池分配给空闲的线程进行执行,线程得到了复用,避免了创建和销毁线程带来的性能损耗。同时,线程池统一管理线程,合理地设置线程数可以提高系统利用率及防止系统宕机。因此,使用线程池解决了上述提到了手动创建线程的缺点。
总结: 1、线程能复用,提高性能 2、能对线程进行管理,保护系统

线程池的核心参数

在这里插入图片描述
线程池的参数总共有7个

  • corePoolSize:核心线程数量,核心线程即常驻线程,创建之后不会销毁。但是设置allowCoreThreadTimeOut为true的话,也会跟非核心线程一样,在没有执行任务一段时间后被销毁。
  • maximumPoolSize:最大线程数量,包括核心线程数和非核心线程数。非核心线程会在没有执行任务一段时间后被销毁。
  • keepAliveTime:保活时间,在非核心线程没有执行任务时,设置的多长时间后被销毁
  • unit:时间单位,配合keepAliveTime使用。如keepAliveTime设为10,unit设为秒,则表示非核心线程没有任务执行时,10s钟后销毁该线程。
  • workQueue:队列,用来暂时存放待执行的任务。
  • threadFactory:创建线程的工厂,定义了线程池怎么创建新线程。可以使用默认的Executors.defaultThreadFactory(),也可以自己实现。
  • rejectHandler:当线程池的线程数达到了maximumPoolSize,并且workQueue存放的任务也满了之后的策略。如AbortPolicy拒绝不接收任务,DiscardOldestPolicy丢弃workQueue中最早的任务。

线程池的创建使用

官方工具类自带

官方的Executors线程池工具类默认实现了三种线程池,尽管作者在注释中推荐,但现在极其不推荐使用:一来这三种会有OOM或线程过多导致CUP使用率占满的情况,二来更推荐开发者根据自身的业务情况,创建合适配置的线程池。

  • CachedThreadPool
	public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

特点:核心线程数为0,最大线程数不限制,使用SynchronousQueue(放入一个任务后阻塞,等有线程取出任务后才能放入)。
这表示线程池不会有常驻线程,但是非核心线程数不受限制,可能导致系统线程数过多而oom

  • FixedThreadPool
	public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

特点:只有核心线程,但任务队列容量不受限制,在任务量激增时无限制地存放进队列中,可能导致OOM

  • SingleThreadExecutor
	public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

特点:有且只有一个核心线程,但任务队列容量不受限制,在任务量激增时无限制地存放进队列中,可能导致OOM

自定义参数使用

public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                10,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        for (int i=0; i<10; i++) {
            final int n = i;
            threadPoolExecutor.execute(()->{
                System.out.println("第" + n +  "个线程在执行");
            });
        }
    }

如上,表示创建了一个核心线程数为2,最大核心线程数为10,10s没执行任务销毁非核心线程,任务队列最多能存放一个任务,默认的ThreadFactory和拒绝策略。通过execute方法传入一个Runnable对象(上面使用了lambda匿名表达式)即提交了一个任务给线程池执行。
而在使用时,我们一般根据实际场景配置参数。

线程池的执行流程

从任务角度:

当一个新的任务交给线程池执行时:首先看当前的线程数是否小于核心线程数,是的话创建一个新的线程来执行该任务(即使此时有线程是空闲的);否的话,看workingQueue是否已满,未满的话将该任务放入workingQueue中;已满的话再看当前线程数是否小于最大线程数(maximumPoolSize),是的话创建一个新的线程来执行该任务,否的话执行RejectExecutionHandler拒绝策略。
在这里插入图片描述

从线程的生命周期角度:

线程池初始时线程数为0(默认,可执行prestartAllCoreThreads初始化所有核心线程)。当第一个任务来时,线程池发现当前的线程数是否小于核心线程数,于是创建出一个新的核心线程来执行任务;核心线程执行完线程后,不会被销毁,会定时地从队列看有没有任务取来执行;而当某一个任务来时,当前线程数大于等于核心线程数,队列又满了,当前线程数又大于等于最大线程数,于是创建非核心线程数来执行该任务;非核心线程执行完之后,也会定时从队列看有没有任务可以执行,同时会开始计时,若keepAliveTime时间内没有任务执行,该线程会被销毁。若在此时间内线程执行了任务则会重新开始计时。

超级进阶:线程池是怎么区分核心线程和非核心线程的?

刚刚介绍线程池的执行流程时,多次提到了核心线程和非核心线程,区别是非核心线程在配置的时间内没有执行任务会被销毁,而核心线程不会。那么,线程池是怎么进行区分核心线程和非核心线程的呢?非核心线程在配置时间没有任务会被销毁,那是在每个非核心线程上有一个计时器吗?

其实线程池是没有区分核心线程和非核心线程的!

线程池只有coresize和maximumsize,在数量上进行的逻辑处理,并没有在线程个体上做区分。 之所以所有的教ba程gu都在强调核心线程和非核心线程,个人猜测是引入核心线程和非核心线程的概念,更便于理解学习。但是又在具体实现上又不加以说明。

当新建了线程,线程首先执行任务;执行完成之后会从workQueue队列中取任务。队列可能为空,因此线程取任务可能会阻塞。在从队列获取任务前,线程池会先进行判断,当线程数量>coreSize时,说明可以消减线程了,就会给该线程设置从队列取任务的最长阻塞时间(keepAliveTime),超时返回null,即表示该线程空闲了keepAliveTime时间,并且线程池数大于corePoolSize核心线程数,就会将该线程销毁。
在这里插入图片描述

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程编程中,配置线程池的参数是非常重要的。根据不同的业务需求,可以设置线程池的参数来控制线程的数量和行为。根据引用中的例子,可以使用`Executors.newFixedThreadPool(int nThreads)`来创建一个固定线程数量的线程池。在这个方法中,`nThreads`参数表示线程池中的线程数量,只有这个数量的线程会被创建。然后,可以使用`pool.execute(Runnable command)`方法来提交任务给线程池执行。 在配置线程池时,需要考虑业务的性质。如果是CPU密集型的任务,比如加密、计算hash等,最佳线程数一般为CPU核心数的1-2倍。而如果是IO密集型的任务,比如读写数据库、文件、网络读写等,最佳线程数一般会大于CPU核心数很多倍。这样可以充分利用IO等待时间来执行其他任务,提高程序的性能。引用中给出了一些常见的线程池特点和构造方法参数。 总之,根据业务需求和特点,合理配置线程池的参数可以提高程序的性能和效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java线程之线程池的参数和配置](https://blog.csdn.net/MRZHQ/article/details/129107342)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java线程之线程池(合理分配资源)](https://blog.csdn.net/m0_52861000/article/details/126869155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java线程之线程池](https://blog.csdn.net/weixin_53611788/article/details/129602719)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值