精华内容
下载资源
问答
  • 1. 线程池自动关闭的情况一:核心线程数为 0 并指定线程存活时间 1.1手动创建线程池 1.2Executors.newCachedThrteadPool() 创建线程池 2线程池自动关闭的情况二:通过 allowCoreThreadTimeOut 控制核心线程存活...

    简介

    首先我们需要了解线程池在什么情况下会自动关闭。 ThreadPoolExecutor 类(这是我们最常用的线程池实现类)的源码注释中有这么一句话:

    在这里插入图片描述

    A pool that is no longer referenced in a program and has no remaining threads will be shutdown automatically.
    没有引用指向且没有剩余线程的线程池将会自动关闭。
    

    那么什么情况下线程池中会没有剩余线程呢?先来看一下 ThreadPoolExecutor 参数最全的构造方法:

    /**
     * @param corePoolSize the number of threads to keep in the pool, even
     * 	      if they are idle, unless {@code allowCoreThreadTimeOut} is set
     *        核心线程数:即使是空闲状态也可以在线程池存活的线程数量,除非       	
     *        allowCoreThreadTimeOut 设置为 true。
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     *		 存活时间:对于超出核心线程数的线程,空闲时间一旦达到存活时间,就会被销毁。
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                         	  RejectedExecutionHandler handler) { ... ... }
    

    这里我们只关心与线程存活状态最紧密相关的两个参数,也就是corePoolSizekeepAliveTime,上述代码块也包含了这两个参数的源码注释和中文翻译。keepAliveTime参数指定了非核心线程的存活时间,非核心线程的空闲时间一旦达到这个值,就会被销毁,而核心线程则会继续存活,只要有线程存活,线程池也就不会自动关闭。聪明的你一定会想到,如果把corePoolSize设置为0,再给keepAliveTime指定一个值的话,那么线程池在空闲一段时间之后,不就可以自动关闭了吗?没错,这就是线程池自动关闭的第一种情况。

    1.线程池自动关闭的情况一:核心线程数为0并指定线程存活时间

    1.1.手动创建线程池

    代码示例:

    public class ThreadPoolTest {
        public static void main(String[] args) {
            // 重点关注 corePoolSize 和 keepAliveTime,其他参数不重要
            ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5,
                    30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
            for (int i = 0; i < 20; i++) {
                executor.execute(() -> {
    			   // 简单地打印当前线程名称
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }
    }
    

    控制台输出结果

    # 线程打印开始
    ... ...
    pool-1-thread-2                    
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-5
    pool-1-thread-1
    # 打印结束,程序等待30s后正常退出
    Process finished with exit code 0   # 小知识:exit code 0 说明程序是正常退出,非强行中断
    

    通过以上代码和运行结果可以得知,在corePoolSize为0且keepAliveTime设置为 30s 的情况下,如果任务执行完毕又没有新的任务到来,线程池里的线程都将消亡,而且没有核心线程阻止线程池关闭,因此线程池也将随之自动关闭。

    而如果将corePoolSize设置为大于0的数字,再运行以上代码,那么线程池将一直处于等待状态而不能关闭,因为核心线程不受keepAliveTime控制,所以会一直存活,程序也将一直不能结束。运行效果如下 (corePoolSize设置为5,其他参数不变)

    控制台输出结果

    # 线程打印开始
    ... ...
    pool-1-thread-5
    pool-1-thread-1
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-2
    # 程序无法结束
    

    1.2Executors.newCachedThrteadPool()创建线程池

    Executors 是 JDK 自带的线程池框架类,包含多个创建不同类型线程池的方法,而其中的**newCachedThrteadPool()**方法也将核心线程数设置为了0并指定了线程存活时间,所以也可以自动关闭。
    其源码如下:

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

    如果用这个线程池运行上面的代码,程序也会自动退出,效果如下

    # 线程打印开始
    ... ...
    pool-1-thread-7
    pool-1-thread-5
    pool-1-thread-4
    pool-1-thread-1
    pool-1-thread-9
    # 打印结束,程序等待60s后退出
    Process finished with exit code 0
    
    

    2.线程池自动关闭的情况二:通过allowCoreThreadTimeOut控制核心线程存活时间

    通过将核心线程数设置为0虽然可以实现线程池的自动关闭,但也存在一些弊端,此话怎讲,先来看一下线程池的执行流程:
    在这里插入图片描述
    在这里插入图片描述
    如图所示,当有新的任务到来时,程序会先判断线程池当前线程数是否达到corePoolSize(核心线程数),没达到则创建线程执行任务,达到则尝试将任务放入任务队列 (workQueue)。如果将corePoolSize设置为0的话,新到来的任务会永远优先被放入任务队列,然后等待被处理,这显然会影响程序的执行效率。那你可能要问了,有没有其他的方法来自己实现可自动关闭的线程池呢?答案是肯定的,从 JDK 1.6 开始,ThreadPoolExecutor 类新增了一个allowCoreThreadTimeOut字段:

    /**
     * If false (default), core threads stay alive even when idle.
     * If true, core threads use keepAliveTime to time out waiting
     * for work.
     * 默认为false,核心线程处于空闲状态也可一直存活
     * 如果设置为true,核心线程的存活状态将受keepAliveTime控制,超时将被销毁
     */
    private volatile boolean allowCoreThreadTimeOut;
    

    这个字段值默认为false,可使用allowCoreThreadTimeOut()方法对其进行设置,如果设置为 true,那么核心线程数也将受keepAliveTime控制,此方法源码如下:

    public void allowCoreThreadTimeOut(boolean value) {
        // 核心线程存活时间必须大于0,一旦开启,keepAliveTime 也必须大于0
        if (value && keepAliveTime <= 0)
            throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
        // 将 allowCoreThreadTimeOut 值设为传入的参数值
        if (value != allowCoreThreadTimeOut) {
            allowCoreThreadTimeOut = value;
            // 开启后,清理所有的超时空闲线程,包括核心线程
            if (value)
                interruptIdleWorkers();
        }
    }
    

    既然如此,接下来我们就借助这个方法实现一个可自动关闭且核心线程数不为0的线程池,这里直接在第一个程序的基础上进行改进:

    public class ThreadPoolTest {
        public static void main(String[] args) {
            // 这里把corePoolSize设为5,keepAliveTime保持不变
            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                    30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
            // 允许核心线程超时销毁
            executor.allowCoreThreadTimeOut(true);
            for (int i = 0; i < 20; i++) {
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }
    }
    

    运行结果

    # 线程打印开始
    ... ...
    pool-1-thread-1
    pool-1-thread-2
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-5
    # 打印结束,程序等待30s后退出
    Process finished with exit code 0
    

    可以看到,程序在打印结束后等待了30s,然后自行退出,说明线程池已自动关闭,
    也就是**allowCoreThreadTimeOut()**方法发挥了作用。这样,我们就实现了可自动关闭且核心线程数不为0的线程池。

    3.超详细的线程池执行流程图

    让我们再来梳理一下更完整的线程池执行流程。
    在这里插入图片描述

    4.结语

    以上就是线程池可以自动关闭的两种情况,而且梳理了详细的线程池执行流程,相信你看完本文一定会有所收获。不过话又说回来,可自动关闭的线程池的实际应用场景并不多,更多时候需要我们手动关闭。下一篇文章我们就来聊聊如何手动关闭线程池,敬请期待!
    原文地址:https://blog.csdn.net/weixin_43207056/article/details/103438809,https://blog.csdn.net/weixin_43207056/article/details/103566392

    展开全文
  • Java线程池

    2019-08-09 17:47:50
    Java线程池

    故事

           某公司
    一、
           工作内容:搬箱子。
           每天搬一个箱子(搬完就下班,任性),新来的箱子怎么办?招人!招人!某公司急需搬箱子员工若干(新来的箱子数量)名,
           朝九晚五,工作轻松,有意者联系。每天箱子数量不固定,好了公司一天很多时间不是结算工资就是招人。 
    二、
           老板想了个办法,调研一下市场,看看公司每个月能来多少个箱子,每天需要处理多少箱子,每个员工一天最多处理多少个箱子。
           回来做一下整理,开始想办法,箱子随时都回来?大半夜让我起来上班怎么能行,建个仓库来了就放在哪里上班时间再弄。每个
           员工每天不能搬一个箱子就下班了,一天搬....就搬6个吧。老板拍了拍肚子上的一块腹肌开心的笑了,第二天就去招固定员工签
           合同。
    三、
           公司几个员工开始上班,“滴滴”,来箱子了,员工甲冲了上去开始工作;滴滴”,又来箱子了,员工甲冲了上去开始工作.......
           核心工作人员都开始忙了,新来的箱子就放在了仓库。如果仓库也满了,老板就会去把轮休的几个员工叫回来加快处理。还忙不
           过来,老板就会想别的解决办法了。
    

    类比

    • 公司 = 应用程序
    • 员工 = 工作线程
    • 招人 = 创建线程
    • 箱子 = 任务
    • 仓库 = 任务队列

      上述故事中公司刚开始对于工作的处理方式,呆呆的,箱子少轻轻松松,箱子多忙的一批。类似传统BIO编程中socket接入的处理方式,监听到新来的socket就创建新线程处理socket IO读写。
      当连接数小的时候可以正常运行,当连接数大的时候会创建很多的线程占用内存(可能导致内存溢出),如此多的线程要运行会导致频繁的CPU调度,即频繁的上下文切换(将当前CPU中运行的线程现场保存,按照调度方式找到下一个幸运的线程来运行)。
      如何解决上述问题呢?——使用线程池

    Java线程池

      通过ThreadPoolExecutor进行分析
    在这里插入图片描述

    Executor接口

      作为线程池的顶级接口,只提供了一个方法。

     void execute(Runnable command)
              在未来某个时间执行给定的命令。提交任务到线程池。
    
    ExecutorService接口

      提供了操作执行程序的方法:

    • 添加了线程池生命周期管理的方法。判断当前执行程序状态是否关闭、关闭后任务是否都完成。shutdown()是拒绝新任务,shutdownNow()拒绝新任务的提交并且试图停止正在执行的所有任务,暂停处理正在等待的任务,返回等待执行的任务列表。

    • 提交一个带返回结果(Future)的任务(Runnable、Callable)的方法submit(…)。

    AbstractExecutorService抽象类

      提供ExecutorService的默认实现。此类使用 newTaskFor 返回的 RunnableFuture 实现 submit、invokeAny 和 invokeAll(批量提交) 方法,实现了如何提交任务和提交的任务类型。

    ThreadPoolExecutor类

      是Java线程池框架的主要实现类。
      构造函数:

    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
    corePoolSize - 基本大小,池中所保存的线程数,包括空闲线程。核心线程数
    maximumPoolSize - 最大大小,池中允许的最大线程数。
    keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
    unit - keepAliveTime 参数的时间单位。
    workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
    threadFactory - 执行程序创建新线程时使用的工厂。
    handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
    

      keepAliveTime指的是当任务执行结束大于核心线程数的空闲线程的存活时间,验证代码如下,等执行一会后,手动在控制台输入111回车即可知道当前池中当前的线程数:

        public static void main(String[] args) {
           ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
                   3,
                   2L,
                   TimeUnit.SECONDS,
                   new ArrayBlockingQueue<>(1));
           //指定空闲线程存活时间2s
           for (int i = 0; i < 20; i++) {
               try {
                   executor.execute(() -> {
                       try {
                           Thread.sleep(1000L);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       System.out.println("running....");
                       System.out.println(executor.getPoolSize());
                   });
               } catch (RejectedExecutionException e) {
                   System.out.println(e.getMessage());
               }
           }
           Scanner scanner = new Scanner(System.in);
           String res = scanner.nextLine();
           if (res.equals("111")) {
               System.out.println(executor.getPoolSize());
           }
       }
    
    线程池运行状态
    • RUNNING:接受新任务并处理排队任务
    • SHUTDOWN:不接受新任务,但处理排队任务
    • STOP:不接受新任务,不处理排队任务,并中断正在进行的任务
    • TIDYING:整理,所有任务都已终止,workerCount为零,线程转换到状态TIDYING将运行terminate()钩子方法
    • TERMINATED:终止,terminate()已完成

    状态切换条件:

    RUNNING -> SHUTDOWN:在调用shutdown()时,可能隐含在finalize()中
    (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()
    SHUTDOWN -> TIDYING:当队列和池都为空时
    STOP -> TIDYING:当池为空时
    TIDYING -> TERMINATED: terminate()钩子方法完成后

    提交任务

    1、如果当前运行线程数小于corePoolSize,创建新的线程执行任务。
    2、否则添加到阻塞任务队列。
    3、如果任务队列满了,尝试创建新线程执行任务,如果当前工作线程数>maximumPoolSize线程池允许的最大线程数。则执行RejectedExecutionHandler。

    拒绝策略
    • AbortPolicy,中止策略,直接抛出异常。
    • CallerRunsPolicy,调用者执行,由调用线程执行任务r.run()。
    • DiscardPolicy,直接删除任务。
    • DiscardOldestPolicy,废弃最旧的任务(排队最久的,即队列头元素),然后重试execute(重复此过程)。

    参考资料

    展开全文
  • 走进Java线程池,解决你对Java线程池的种种疑问 (二) Executors是一个Java中的工具类,提供工厂方法来创建不同类型的线程池。 前面我们已经介绍了关于Executors工具类中常用的创建线程池的方法,我们简单进行回顾...

    Executors是一个Java中的工具类,提供工厂方法来创建不同类型的线程池。
    在这里插入图片描述

    前面我们已经介绍了关于Executors工具类中常用的创建线程池的方法,我们简单进行回顾下:

    • 1.newSingleThreadExecutor
    newSingleThreadExecutor
    介绍:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来代替它。此线程池保证所有的任务的执行顺序按照任务的提交顺序执行。
    优点:单线程的线程池,保证线程的顺序执行
    缺点:不适合并发
    • 2.newFixedThreadPool
    newFixedThreadPool
    介绍:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大值。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
    优点:固定大小线程池,超出的线程会在队列中等待
    缺点:不支持自定义拒绝策略,大小拒绝,难以扩展
    • 3.newCachedThreadPool
    newCachedThreadPool
    介绍:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程。当任务增加时,此线程又可以添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者JVM)能够创建的最大线程大小。
    优点:很灵活,具有弹性的线程池线程管理,用多少线程池给多大的线程池,不用后及时进行回收,用时可以进行新建
    缺点:一旦线程无限增长,会导致内存溢出
    • newScheduledThreadPool
    newScheduledThreadPool
    介绍:创建一个定长线程池,支持定时与周期性任务的执行
    优点:一个固定大小的线程池,可以定时或者周期性执行任务
    缺点:任务是单线程方式执行,一旦一个任务失败其他任务也会受到影响

    注意:

    1.以上线程池都不支持自定义拒绝策略
    2.newFixedThreadPool和newSingleThreadExecutor:
    主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至会导致内存溢出

    3.newCachedThreadPool和newScheduledPool:
    主要问题是线程最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至内存溢出

    由于Executors中提供创建线程池的方法具有以上的弊端,所以会推荐使用ThreadPoolExecutor创建线程池,集以上优点于一身。


    1.对ThreadPoolExecutor的理解

    Java提供了实现的线程池的模型–ThreadPoolExecutor,可以这样理解Java线程池:就是为了最大化高并发带来的性能提升,并最小化手动创建线程的风险,将多个线程统一在一起管理的思想。

    • 首先,我们需要关注ThreadPoolExecutor的构造方法:
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    
    

    细看ThreadPoolExecutor的构造方法还是比较复杂的,下面我们探讨下构造方法中的核心参数。


    1.1 ThreadPoolExecutor核心参数理解(面试常问)

    参数名称 参数解释
    corePoolSize 表示常驻核心线程数,如果大于0,即使本地任务执行完也不会被销毁。
    maximumPoolSize 表示线程池能够容纳可同时执行的最大线程数
    keepAliveTime 表示线程池中线程空闲的时间,当空闲时间达到该值时,线程会被销毁,只剩下corePoolSize个线程位置
    unit keepAliveTime的时间单位,最终都会转换为纳秒
    workQueue 当请求线程数大于maximumPoolSize时,线程进入该阻塞队列
    threadFactory 表示线程工厂,用来生产一组相同任务的线程,同时也可以通过增加前缀名,虚拟机栈分析时会更加清晰
    handler 执行拒绝策略,当workQueue达到上限,同时也达到maximumPoolSize就要通handler来进行处理。例如拒绝,丢弃等,这是一种限流的保护措施

    整体任务执行如下:

    试想,如果有请求就创建一个线程,请求结束就销毁一个线程,频繁往复这样操作,这样的代价是不能接受的。

    可以看到,使用线程池不但完成手动创建线程可以做到的工作,同时也填补了手动线程不能做到的空白。

    由此可见线程池的作用可以包含:

    1.实现任务线程队列缓存策略和拒绝机制
    2.利用线程池管理线程,控制最大并发数(手动创建线程很难得到保证)
    3.可以实现定时执行,周期执行等功能


    1.2 线程池中任务的执行过程分析

    当任务提交到线程池中需要经过以下过程:

    在这里插入图片描述

    执行流程解析
    1.首先检查核心线程池是否已满。这个核心线程池,就是不管用户量多少,线程池始终维持的线程的池子。在这里可以假如说线程池的总容量装100个线程,核心线程数我们设置为50,那么无论用户量有多少,都保持50个线程存活着。核心线程池中线程数量是根据具体业务需求来决定的。
    2.判断阻塞队列是否已满,阻塞队列有很多种,在下面也会简单介绍各种阻塞队列
    3.最后判断线程池是否已满。按照前面的例子,就是判断线程数是不是100个线程,而不是50个。
    4.如果满了,就不能继续创建线程了,就需要按照饱和策略或者拒绝策略来进行处理。拒绝策略在下面也会简单介绍。

    简单来说:有请求时,创建线程执行任务,当线程数量等于corePoolSize(常驻核心线程数),请求假如阻塞队列里,当队列满了时,接着创建线程,线程数等于maximumPoolSize。当任务处理不过来的时候,线程池开始执行拒绝策略。


    1.3 阻塞队列的介绍

    • 何为阻塞?简单的来说,食堂打饭的窗口只有一个,下课了同时来了很多同学,窗口每次只能服务于一个学生。剩下的学生只能排队等待。

    • 何为队列?先进先出的一种数据结构(容器),例如:食堂排队打饭。

    • 什么时候使用阻塞队列?生产者消费者模式

    阻塞队列的工作流程:

    在这里插入图片描述

    • 下面是阻塞队列的介绍:
    阻塞队列的名称 含义
    ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
    LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列,默认大小为Integer.MAX_VALUE
    PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
    DelayQueue 一个使用优先级队列实现的无界阻塞队列}
    SynchronousQueue 一个不存储元素的阻塞队列。如果生产者线程将元素放入队列,消费者线程没有从队列中拿走元素,那么该队列会进入阻塞状态
    LinkedTransferQueue 一个由链表结构组成的无界阻塞队列
    LinkedBlockingDueue 一个由链表结构组成的双向阻塞队列

    1.4 线程池拒绝策略

    我们很难准确的预测未来的最大并发量,所以定制合理的拒绝策略是不可忽略的步骤。ThreadPoolExecutor提供了四种拒绝策略:

    在这里插入图片描述

    • 1.AbortPolicy:默认的拒绝策略,会throw RejectedExecutionException拒绝
    • 2.CallerRunsPolicy:提交任务的线程自己去执行该任务
    • 3.DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列
    • 4.DiscardPolicy:相当大胆的策略,直接丢弃任务,没有任何异常抛出

    不同的框架也都有不同的拒绝策略,我们也可以通过实现RejectedExecutionHandler 自定义的拒绝策略。

    没有绝对的拒绝策略,只有适合业务需求的拒绝策略,但在设计过程中千万不要忽略掉拒绝策略就可以。


    总结

    当我们需要频繁的创建线程时,我们要考虑到利用线程池统一管理线程资源,避免额外的开销和不可控的风险。同时了解到了线程池的几个核心参数之后,我们需要经过调优的过程来设置最佳线程参考值。希望大家看到此文对大家线程池部分的学习有所帮助。
    在这里插入图片描述

    展开全文
  • 20210728Java线程池

    2021-07-28 03:03:23
    20210728Java线程池 编辑时间:2021/07/28 读完本节:大概花费不到20分钟,共2469词 文章目录20210728Java线程池1.使用线程池的优点2.线程池的状态3.线程池的分类4....形成内存溢出,或者CPU耗尽。 提高线程

    20210728Java线程池

    编辑时间:2021/07/28

    读完本节:大概花费不到20分钟,共2469词

    1.使用线程池的优点

    1. 降低资源销毁

      通过重复利用已经创建的线程,降低线程创建和销毁造成的消耗

    2. 提高响应速度

      当任务到达时,任务可以不需要等到线程创建就能立即执行

    3. 防止服务器过载

      形成内存溢出,或者CPU耗尽。

    4. 提高线程的可管理性

      线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

    2.线程池的状态

    1. SHUT DOWN

      表示不接受新任务,但可执行队列中的任务

    2. TIDYING

      所有任务已经中止,且工作线程数量为0

    3. RUNNING

      表示可接受新任务,且可执行队列中的任务

    4. STOP

      不接受新任务,且不再执行队列中的任务,且中断正在执行的任务

    5. TERMINATED

      中止状态

    6. 线程池的状态有限机

      截屏2021-07-28 上午1.21.59

    3.线程池的分类

    1. ThreadPoolExecutor

      1. ThreadPoolExecutor的继承关系:

        ThreadPoolExecutor继承于AbstractExecutorService,AbstractExecutorService继承于ExecutorService,ExecutorService继承于Executor

      2. ThreadPoolExecutor的其中一个构造方法

        /**
        * Creates a new {@code ThreadPoolExecutor} with the given initial
        * parameters.
        *
        * @param corePoolSize the number of threads to keep in the pool, even
        *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
        * @param maximumPoolSize the maximum number of threads to allow in the
        *        pool
        * @param keepAliveTime when the number of threads is greater than
        *        the core, this is the maximum time that excess idle threads
        *        will wait for new tasks before terminating.
        * @param unit the time unit for the {@code keepAliveTime} argument
        * @param workQueue the queue to use for holding tasks before they are
        *        executed.  This queue will hold only the {@code Runnable}
        *        tasks submitted by the {@code execute} method.
        * @param threadFactory the factory to use when the executor
        *        creates a new thread
        * @param handler the handler to use when execution is blocked
        *        because the thread bounds and queue capacities are reached
        * @throws IllegalArgumentException if one of the following holds:<br>
        *         {@code corePoolSize < 0}<br>
        *         {@code keepAliveTime < 0}<br>
        *         {@code maximumPoolSize <= 0}<br>
        *         {@code maximumPoolSize < corePoolSize}
        * @throws NullPointerException if {@code workQueue}
        *         or {@code threadFactory} or {@code handler} is null
        */
        public ThreadPoolExecutor(int corePoolSize,
                                      int maximumPoolSize,
                                      long keepAliveTime,
                                      TimeUnit unit,
                                      BlockingQueue<Runnable> workQueue,
                                      ThreadFactory threadFactory,
                                      RejectedExecutionHandler handler) {
        	if (corePoolSize < 0 ||
                    maximumPoolSize <= 0 ||
                    maximumPoolSize < corePoolSize ||
                    keepAliveTime < 0)
                    throw new IllegalArgumentException();
        	if (workQueue == null || threadFactory == null || handler == null)
                    throw new NullPointerException();
        	this.acc = System.getSecurityManager() == null ?
        						null :
        						AccessController.getContext();
        	this.corePoolSize = corePoolSize;
        	this.maximumPoolSize = maximumPoolSize;
        	this.workQueue = workQueue;
        	this.keepAliveTime = unit.toNanos(keepAliveTime);
        	this.threadFactory = threadFactory;
        	this.handler = handler;
        }
        
      3. 构造方法中型参的作用

        corePoolSize:线程池中的核心线程数量,在没有用的时候,也不会被回收。

        maximumPoolSize:就是线程池中可以容纳的最大线程的数量

        keepAliveTime:线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间

        util:计算时间的一个单位

        workQueue:就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)

        handler:拒绝策略

        threadFactory:一个用于创建新线程的工厂

      4. 线程池一个任务的提交过程

        截屏2021-07-28 上午1.51.07

      5. 线程池的拒绝策略

        如何触发拒绝策略:当任务队列满了之后,如果还有任务提交过来,就会触发拒绝策略

        四种常用的拒绝策略:

        pAbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认该方式。

        pCallerRunsPolicy:直接调用execute来执行当前任务。

        pDiscardPolicy:丢弃任务,但是不抛出异常。

        pDiscardOldestPolicy:抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。先从任务队列中弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。

      6. 四种常用的线程池

        1. newCachedThreadPool

          创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

          底层源码实际上是new了一个ThreadPoolExecutor,它没有核心线程,最大线程可以有多个,然后保活60秒,60秒内无使用,就被回收。它的任务队列用的是SynchronousQueue。没有指定它的线程工厂,它的线程工厂用的是默认的线程工厂。也没有拒绝策略,使用默认拒绝策略。

          newCachedThreadPool的特点:一旦收到新任务就必须马上执行,没有线程空着就new线程,这样会导致启动的线程特别多,基本没有上限,导致系统开销大,但是能够迅速响应线程执行的要求

          使用举例

          ExecutorService pool = Executors.newCachedThreadPool();
          
          for(int i = 0; i < 10; i++){
            	Thread.sleep(200);
          		pool.execute(()->{
                	for(int s = 0; s < 10; s++){
                    	System.out.println(Thread.currentThread.getName() + ":" s);
                    	try{
                        	Thread.sleep(200);
                      }catch(InterruptedException e){
                        	e.printStackTrace();
                      }
                  }
              });  
          }
          
        2. newFixedThreadPool

          创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

          FixedThreadPool到底有几个线程是由核心线程数和最大线程数确定的。底层源码

          /**
          * Creates a thread pool that reuses a fixed number of threads
          * operating off a shared unbounded queue.  At any point, at most
          * {@code nThreads} threads will be active processing tasks.
          * If additional tasks are submitted when all threads are active,
          * they will wait in the queue until a thread is available.
          * If any thread terminates due to a failure during execution
          * prior to shutdown, a new one will take its place if needed to
          * execute subsequent tasks.  The threads in the pool will exist
          * until it is explicitly {@link ExecutorService#shutdown shutdown}.
          *
          * @param nThreads the number of threads in the pool
          * @return the newly created thread pool
          * @throws IllegalArgumentException if {@code nThreads <= 0}
          */
          public static ExecutorService newFixedThreadPool(int nThreads) {
              return new ThreadPoolExecutor(nThreads, nThreads,
                                            0L, TimeUnit.MILLISECONDS,
                                            new LinkedBlockingQueue<Runnable>());
          }
          

          底层实际还是使用ThreadPoolExecutor创建线程池,但是corePoolSize大小和maximumPoolSize都被确定了,也因为如此不需要进行回收,所以保活时间keepAliveTime设置为0L。

          newFixedThreadPool使用举例

          ExecutorService pool = Executors.newFixedThreadPool(2);
          
          for(int i = 0; i < 10; i++){
            	Thread.sleep(200);
          		pool.execute(()->{
                	for(int s = 0; s < 10; s++){
                    	System.out.println(Thread.currentThread.getName() + ":" s);
                    	try{
                        	Thread.sleep(200);
                      }catch(InterruptedException e){
                        	e.printStackTrace();
                      }
                  }
              });  
          }
          
        3. newScheduledThreadPool

          创建一个定长线程池,支持定时及周期性任务执行。

          newScheduledThreadPool使用举例

          ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
          int t = 0;
          pool.execute(()->{
            	System.out.println(
              		Thread.currentThread.getName() + ":" + t,
              		0,
              		2,
              		TimeUnit.SECONDS);
          });
          
        4. newSingleThreadExecutor

          创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

          newSingleThreadExecutor使用举例

          ExecutorService pool = Executors.newSingleThreadExecutor();
          
          for(int i = 0; i < 10; i++){
            	Thread.sleep(200);
          		pool.execute(()->{
                	for(int s = 0; s < 10; s++){
                    	System.out.println(Thread.currentThread.getName() + ":" s);
                    	try{
                        	Thread.sleep(200);
                      }catch(InterruptedException e){
                        	e.printStackTrace();
                      }
                  }
              });  
          }
          
    2. ForkJoinPool

      思想:将一个任务划分成若干个子任务,然后分别在多线程的情况下运行,最后汇总结果并输出

      使用ThreadPoolExecutor定义任务的时候是从Runnable来继承,在实现ForkJoinPool的时候需要定义称谓特定的task类型,这个类型必须得能进行分叉的任务,所以将这个task定义成是一种特殊类型的任务,叫做ForkJoinTask。实际上,这个ForkJoinTask比较原始,可以用RecursiveAction。RecursiveAction其中一种叫RecursiveAction递归,称为递归的原因是:大任务分解成小任务,一直可以切到满足条件为止,这其中隐含了一个递归过程,因此叫RecursiveAction,这个RecursiveAction是不带返回值的任务。

    4.如何调整线程池的大小

    1. 如果线程池中的线程数量过多,最终他们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上,反之,如果线程池的数目过少,导致一些核可能无法充分利用。线程池的大小与处理器的利用率之比可以使用下面的公式进行估算

    Nthread = NCPU * UCPU * (1 + W/C)

    其中

    NCPU是处理器的核心数目,可以通过Runtime.getRuntime().availableProcessors()获取

    UCPU是期望的CPU利用率,这个值介于0和1之间

    W/C是等待时间与计算时间的比率

    5.线程池的使用情景

    1. 高并发、任务执行时间短的业务怎样使用线程池?

      高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

    2. 并发不高、任务执行时间长的业务怎样使用线程池?

      假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。

      假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换

    6.线程池的提交方式

    1. submit

      1. submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。

      2. submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常

      3. 使用

        Executor pool = Executors.newFixedThreadPool(10);
        
        //runnable 拉姆达表达式,包含返回值
        Future<?> submit = pool.submit(()->{
          	System.out.printn(
            		Thread.currentThread().getName() + ">>> test submit..."
            );
        });
        
    2. execute

      1. execute只能提交Runnable类型的任务,无返回值。

      2. execute在执行任务时,如果遇到异常会直接抛出

      3. 使用

        Executor pool = Executors.newFixedThreadPool(10);
        
        //runnable 拉姆达表达式
        pool.execute(()->{
          	System.out.printn(
            		Thread.currentThread().getName() + ">>> test execute..."
            );
        });
        
    无限进步
    展开全文
  • java线程池

    2020-05-11 14:44:47
    java线程池 1.java的线程池 线程池是一种多线程处理形式,处理过程中将任务添加队列,然后在创建线程后自动启动这些任务。 2.线程池的顶级接口 线程池的顶级接口是Executor,Executor并不是一个线程池,而只是一个...
  • 为什么需要线程池2.java线程池的实现3.创建线程的工厂方法Executors3.1 newFixedThreadPool3.2 newSingleThreadExecutor3.3 newCachedThreadPool2.4 newScheduledThreadPool3.5 newWorkStealingPool4.线程池的使用...
  • Java线程池总结

    2021-07-27 16:33:53
    Java线程池总结 一、线程池的概念及优势 ​ 线程在使用时需要创建,使用完需要销毁,而创建、销毁线程会消耗系统资源(内存、CPU),重复操作会导致大量资源浪费。从而就需要使用池化技术----线程池,减少线程对象的...
  • 问题:java程序内存溢出java.lang.OutOfMemoryError: GC overhead limit exceeded问题 1)截取log4j相关日志: java.net.SocketException: Socket input is already shutdown at java.net.Socket.shutdownInput...
  • java 线程池

    2020-08-27 16:11:03
    因为线程若是无限制的创建,可能会导致内存占用过多而产生内存溢出,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。 (4)提供更强大的功能,延时...
  • 深入Java线程池:从设计思想到源码解读

    万次阅读 多人点赞 2021-02-14 11:26:10
    本文由浅入深,阐述下面4大内容,深入解读线程池 1、线程池的优势 2、线程池的原理 3、线程池的使用 4、线程池的源码解读
  • JAVA线程池解析

    2020-08-17 11:17:42
    JAVA线程池 为什么要用线程池 概念 我们在数据库连接池中也接触过,那线程池顾名思义就是存放线程的池子,是一个容器,我们预先创建若干线程放在容器中,使用的时候直接拿来用,用完放回去。 优势 线程的创建与销毁...
  • JAVA线程池

    2021-05-15 13:39:14
    线程池原理及使用浅析1 线程池概述1.1 为什么使用线程池1.2 线程池的状态2 线程池底层原理3 线程池使用3.1 自定义线程池3.2 预定义线程池3.2.1 FixedThreadPool3.2.2 SingleThreadExecutor3.2.3 CachedThreadPool...
  • java线程池配置详解

    2021-07-31 14:55:15
    java线程池配置详解 ThreadPoolExecutor全参构造 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,200
精华内容 8,480
关键字:

java线程池内存溢出

java 订阅