栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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());
    }

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

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

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

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

特点:有且只有一个核心线程,但任务队列容量不受限制,在任务量激增时无限制地存放进队列中,可能导致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核心线程数,就会将该线程销毁。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/685821.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号