zoukankan      html  css  js  c++  java
  • Java多线程-线程池原理

    1.JUC线程池结构

    JUC 就是 java.util .concurrent 工具包的简称,该工具包是从 JDK 1.5 开始加入到 JDK,用于完成高并发、处理多线程的一个工具包。
      在多线程编程中,任务都是一些抽象且离散的工作单元,而线程是使任务异步执行的基本机制。随着应用的扩张,线程和任务管理也变得非常复杂,为了简化这些复杂的线程管理模式,
    我们需要一个“管理者”来统一管理线程及任务分配,这就是线程池。

    1.1.Executor

      它是 Java 异步目标任务的“执行者”接口,其目标是来执行目标任务。“执行者”Executor提供了 execute()接口来执行已提交的 Runnable 执行目标实例。
    Executor 作为执行者的角色,存在的目的是“任务提交者”与“任务执行者”分离开来的机制。它只包含一个函数式方法:
    void execute(Runnable command)

    1.2.ExecutorService

      ExecutorService 继承于 Executor。它是 Java 异步目标任务的“执行者服务“接口,它对外提供异步任务的接收服务,ExecutorService 提供了“接收异步任务、并转交给执行者”的方法,
    如submit 系列方法、invoke 系列方法等等。具体如下:
    //向线程池提交单个异步任务
    <T> Future<T> submit(Callable<T> task);
    //向线程池提交批量异步任务
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
     throws InterruptedException;

    1.3.AbstractExecutorService

    AbstractExecutorService 是 一 个 抽 象 类 , 它 实 现 了 ExecutorService 接口。
    AbstractExecutorService 存在的目的是为 ExecutorService 中的接口提供了默认实现。

    1.4.ThreadPoolExecutor

      ThreadPoolExecutor 就是大名鼎鼎的“线程池”实现类,它继承于 AbstractExecutorService 抽象类。
      ThreadPoolExecutor 是 JUC 线程池的核心实现类。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,
    并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。

    1.5.ScheduledExecutorService

      ScheduledExecutorService 是一个接口,它继承于于 ExecutorService。它是一个可以完成“延
    时”“周期性”任务的调度线程池接口,其功能和 Timer/TimerTask 类似。

    1.6.ScheduledThreadPoolExecutor

      ScheduledThreadPoolExecutor 继承于 ThreadPoolExecutor,它提供了 ScheduledExecutorService
    线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。
      ScheduledThreadPoolExecutor类似于Timer , 但 是 在 高 并 发 程 序 中 ,
    ScheduledThreadPoolExecutor 的性能要优于 Timer。

    1.7.Executors

      Executors 是个静态工厂类,它通 过 静 态 工 厂 方 法 返 回 ExecutorService 、
    ScheduledExecutorService 等线程池示例对象,这些静态工厂方法可以理解为一些快捷的创建线程池的方法
     

    2.Executors快捷创建线程池

    2.1.newSingleThreadExecutor 创建“单线程化线程池”

      该方法用于创建一个“单线程化线程池”,也就是只有一条线程的线程池,所创建的线程池
    用唯一的工作线程来执行任务,使用此方法创建的线程池,能保证所有任务按照指定顺序(如FIFO)执行。

    2.2.newFixedThreadPool 创建“固定数量的线程池”

    该方法用于创建一个“固定数量的线程池”,其唯一的参数用于设置池中线程的“固定数量”。
    “固定数量的线程池”的特点,大致如下:
    (1)如果线程数没有达到“固定数量”,则每次提交一个任务池内就创建一个新线程,直到
    线程达到线程池的固定的数量。
    (2)线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结
    束,那么线程池会补充一个新线程。
    (3)在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,对于新任务会
    进入阻塞队列中(无界的阻塞队列)。
     
    “固定数量的线程池”的适用场景:需要任务长期执行的场景。“固定数量的线程池”的线程
    数能够比较稳定保证一个数,能够避免频繁回收线程和创建线程,故适用于处理 CPU 密集型的任
    务,在 CPU 被工作线程长时间使用的情况下,能确保尽可能少的分配线程。
    “固定数量的线程池”的弊端:内部使用无界队列来存放排队任务,当大量任务超过线程池
    最大容量需要处理时,队列无线增大,使服务器资源迅速耗尽。

    2.3.newCachedThreadPool 创建“可缓存线程池”

    该方法用于创建一个“可缓存线程池”,如果线程池内的某些线程无事可干成为空闲线程,“可缓存线程池”可灵活回收这些空闲线程。
    “可缓存线程池”的特点,大致如下:
    (1)在接收新的异步任务 target 执行目标实例时,如果池内所有线程繁忙,此线程池会添加
    新线程来处理任务。
    (2)此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)
    能够创建的最大线程大小。
    (3)如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,那么就会回收空闲
    (60 秒不执行任务)线程。
    “可缓存线程池”的适用场景:需要快速处理突发性强、耗时较短的任务场景,如 Netty 的
    NIO 处理场景、REST API 接口的瞬时削峰场景。“可缓存线程池”的线程数量不固定,只要有空
    闲线程就会被回收;接收到的新异步任务执行目标,查看是否有线程处于空闲状态,如果没有就
    直接创建新的线程。
    “可缓存线程池”的弊端:线程池没有最大线程数量限制,如果大量的异步任务执行目标实
    例同时提交,可能导致创线程过多会而导致资源耗尽。

    2.4.newScheduledThreadPool 创建“可调度线程池”

    该方法用于创建一个“可调度线程池”,一个提供“延时”和“周期性”任务的调度功能的
    ScheduledExecutorService 类型的线程池。
     
    以上是通过 JUC 的 Executors 四个主要的快捷创建线程池方法。为何 JUC 要提供工厂方法呢?
    原因是使用 ThreadPoolExecutor、ScheduledThreadPoolExecutor 构造器去创建普通线程池、可调度
    线程池比较复杂,这些构造器会涉及到大量的复杂参数。尽管 Executors 的工厂方法使用方便,但
    是在生产场景被很多企业(尤其是大厂)的开发规范所禁用。

    3.线程池的标准创建方式

    大部分企业的开发规范,都会禁止使用快捷线程池(具体原因稍后介绍),要求通过标准构
    造器 ThreadPoolExecutor 去构造工作线程池。实质上,Executors 工厂类中创建线程池的快捷工厂
    方法,实际上是调用了 ThreadPoolExecutor(定时任务使用 ScheduledThreadPoolExecutor )线程
    池的构造方法完成的。ThreadPoolExecutor 构造方法有多个重载版本,其中一个比较重要的构造器
    如下:
    // 使用标准构造器,构造一个普通的线程池
    public ThreadPoolExecutor(
     int corePoolSize, // 核心线程数,即使线程空闲(Idle),也不会回收;
     int maximumPoolSize, // 线程数的上限;
     long keepAliveTime, TimeUnit unit, // 线程最大空闲(Idle)时长
     BlockingQueue<Runnable> workQueue, // 任务的排队队列
     ThreadFactory threadFactory, // 新线程的产生方式
     RejectedExecutionHandler handler) // 拒绝策略
    很无奈,构造一个线程池竟然有 7 个参数,但是确实需要这么多参数。接下来对这些参数做
    一下具体介绍

    3.1核心和最大线程数量

    参数 corePoolSize 用于设置核心(Core)线程池数量,参数 maximumPoolSize 用于设置最大
    线程数量。线程池执行器将会根据 corePoolSize 和 maximumPoolSize 自动地维护线程池中的工作
    线程,大致的规则为:
    (1)当在线程池接收到的新任务,并且当前工作线程数少于 corePoolSize 时,即使其他工作
    线程处于空闲状态,也会创建一个新线程来处理该请求,直到线程数达到 corePoolSize。
    (2)如果当前工作线程数多于 corePoolSize 数量,但小于 maximumPoolSize 数量,则仅当任
    务排队队列已满时,才会创建新线程。 通过设置 corePoolSize 和 maximumPoolSize 相同,可以创
    建一个固定大小的线程池。
    (3)当 maximumPoolSize 被设置为无界值(如 Integer.MAX_VALUE)时,线程池可以接收
    任意数量的并发任务。
    ( 4 ) corePoolSize 和 maximumPoolSize 不 仅 能 在 线 程 池 构 造 时 设 置 , 也 可 以 使 用
    setCorePoolSize 和 setMaximumPoolSize 两个方法进行动态更改

    3.2.BlockingQueue

    BlockingQueue(阻塞队列)的实例用于暂时接收到的异步任务,如果线程池的核心线程都在
    忙,则所接收到的目标任务,缓存在阻塞队列中。

    3.3.keepAliveTime

    线程构造器的 keepAliveTime(空闲线程存活时间)参数,用于设置池内线程最大 Idle(空闲)
    时长或者说保活时长,如果超过这个时间,默认情况下 Idle、非 Core 线程会被回收。
    如果池在使用过程中,提交任务的频率变高,也可以使用方法 setKeepAliveTime(long,
    TimeUnit)进行线程存活时间的动态调整,可以时长延长。如果需要防止 Idle(空闲)线程被终
    止,可以将 Idle(空闲)时间设置为无限大,具体如下:
    setKeepAliveTime(Long.MAX_VALUE,TimeUnit.NANOSECONDS);
    默认情况下,Idle 超时策略仅适用于存在超过 corePoolSize 线程的情况。 但是如果调用了
    allowCoreThreadTimeOut(boolean)方法,并且传入了参数 true,则 keepAliveTime 参数所设置的 Idle
    超时策略也将被应用于核心线程。

    4.线程池的拒绝策略

    在线程池的任务缓存队列为有界队列(有容量限制的队列)的时候,如果队列满了,提交任
    务到线程池的时候就会被拒绝。总体来说,任务被拒绝有两种情况:
    (1)线程池已经被关闭。
    (2)工作队列已满且 maximumPoolSize 已满。
    无论以上哪种情况任务被拒,线程池都会调用 RejectedExecutionHandler 实例的
    rejectedExecution 方法。RejectedExecutionHandler 是拒绝策略的接口,JUC 为该接口提供了以下几
    种实现:
    ⚫AbortPolicy:拒绝策略
    ⚫DiscardPolicy:抛弃策略
    ⚫DiscardOldestPolicy:抛弃最老任务策略
    ⚫CallerRunsPolicy:调用者执行策略
    ⚫自定义策略
    (1)AbortPolicy
    使用该策略时,如果线程池队列满了则新任务被拒绝,并且会抛出 RejectedExecutionException
    异常。该策略是线程池的默认的拒绝策略。
    (2)DiscardPolicy
    该策略是 AbortPolicy 的 Silent(安静)版本,如果线程池队列满了,新任务会直接被丢掉,
    并且不会有任何异常抛出。
    (3)DiscardOldestPolicy
    抛弃最老任务策略,也就是说如果队列满了,会将最早进入队列的任务抛弃,从队列中腾出
    空间,再尝试加入队列。因为队列是队尾进队头出,队头元素是最老的,所以每次都是移除对头
    元素后再尝试入队。
    (4)CallerRunsPolicy
    调用者执行策略。在新任务被添加到线程池时,如果添加失败,那么提交任务线程会自己去
    执行该任务,不会使用线程池中的线程去执行新任务。
    在以上的四种内置策略中,线程池默认的拒绝策略为 AbortPolicy,如果提交的任务被拒绝,
    线程池抛出 RejectedExecutionException 异常,该异常是非受检异常(运行时异常),很容易忘记
    捕获。如果关心任务被拒绝的事件,需要在提交任务时捕获 RejectedExecutionException 异常。
    (5)自定义策略
    如果以上拒绝策略都不符合需求,则可自定义一个拒绝策略,实现 RejectedExecutionHandler
    接口的 rejectedExecution 方法即可

     
    注重细节——关注底层——注重细节——关注底层——注重细节——关注底层——注重细节——关注底层——注重细节——关注底层
  • 相关阅读:
    asp.net core mvc 之 DynamicApi
    打造适用于c#的feign
    asp.net App_Code文件夹相关操作
    基于Mono.Cecil的静态注入
    补充ICache
    自制简单实用IoC
    自制简单的.Net ORM框架 (一) 简介
    解决Asp.net Mvc中使用异步的时候HttpContext.Current为null的方法
    微信开发之.Net
    VS2017 网站打包发布生成的文件中包含.pdb文件,解决办法
  • 原文地址:https://www.cnblogs.com/PJG20/p/15061337.html
Copyright © 2011-2022 走看看