1

线程池的创建参数

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

各个参数的含义

  • corePoolSize: 线程池核心线程数
  • maximumPoolSize:线程池最大数
  • keepAliveTime: 空闲线程存活时间
  • unit: 时间单位
  • workQueue: 线程池所使用的缓冲队列
  • threadFactory:线程池创建线程使用的工厂
  • handler: 线程池对拒绝任务的处理策略

增减线程的特点

  • 当池中正在运行的线程数(包括空闲线程)小于corePoolSize时,新建线程执行任务。
  • 当池中正在运行的线程数大于等于corePoolSize时,新插入的任务进入workQueue排队(如果workQueue长度允许),等待空闲线程来执行。
  • 当队列里的任务数当队列里的任务数达到上限,并且池中正在运行的线程数小于maximumPoolSize,对于新加入的任务,新建线程。达到上限,并且池中正在运行的线程数小于maximumPoolSize,对于新加入的任务,新建线程
  • 当队列里的任务数达到上限,并且池中正在运行的线程数等于maximumPoolSize,对于新加入的任务,执行拒绝策略(线程池默认的拒绝策略是抛异常)

注意点:通过设置corePoolSize和maximumPoolSize相同,可以创建固定大小的线程池;通过设置maximumPoolSize数量为很高的值,如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务;通过设置workQueue为无界队列例如LinkedBlockingQueue,线程数就不会超过corePoolSize

3种工作队列的类型(都继承BlockingQueue,因为需要并发)

  • 直接提交:工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性
  • 无界队列:使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性
  • 有界队列:当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

线程池推荐手动创建(自动创建容易导致OOM异常,因为各种无界队列和线程池无限大小)

几种Java自动创建的线程池类型

  • newCachedThreadPool创建一个可缓存线程池程,是一种线程数量不定的线程池,并且其最大线程数为Integer.MAX_VALUE,一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这类线程池比较适合执行大量的耗时较少的任务,当整个线程池都处于闲置状态时,线程池中的线程都会超时被停止
  • newFixedThreadPool创建一个指定工作线程数量的线程池,每当提交一个任务就创建一个工作线程,当线程 处于空闲状态时,它们并不会被回收,除非线程池被关闭了,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列(没有大小限制)中
  • newScheduledThreadPool创建一个定长线程池 ,可以定时,周期的执行任务(DelayQueue) ,它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收,它可安排给定延迟后运行命令或者定期地执行。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
  • newSingleThreadExecutor创建一个单线程化的线程池 只有一个thread处理所有的任务(利用SynchronousQueue) , 内部只有一个核心线程,以无界队列方式来执行该线程,这使得这些任务之间不需要处理线程同步的问题,它确保所有的任务都在同一个线程中按顺序中执行,并且可以在任意给定的时间不会有多个线程是活动的
  • JDK8之后加入了新的newWorkStealingPool:其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,也就是说当任务会产生子任务时,其他线程可以帮助处理该子任务, 不保证处理顺序。 适合于递归,并且不加锁的情况

1583889506677.png

手动创建线程池线程数量的大小设定

CPU密集的任务,线程数可以是核心数的一到两倍

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。所以cpu的计算能力已经满负荷,再来更多的线程也没有办法提高效率,所以线程数不宜过多

IO密集的任务,线程数可以是核心数的很多倍倍

IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,因为大部分线程其实是在等待IO,没有用到cpu计算,cpu的处理能力还有剩余

处理公式

最佳线程数目 = ((线程平均等待时间/线程平均CPU时间)+1 )* CPU数目

等待时间和运行时间的比值越高,线程数目就可以增加的越多

精确数字可以通过压力测试取得

停止线程池的相关方法

  • shutdown()只是起到通知的作用,线程池不会再接收新任务,但是正在运行和存放在工作队列里的任务会运行完毕
  • shutdownNow()会尝试interrupt线程池中正在执行的线程,等待执行的线程也会被取消,但是并不能保证一定能成功的interrupt线程池中的线程。 会返回未完成的任务队列中的任务列表 List<Runnable>
  • awaitTermination(n, TimeUnit)该方法返回值为boolean类型,方法的两个参数规定了方法的阻塞时间,在阻塞时间内除非所有线程都执行完毕才会提前返回true,如果到了规定的时间,线程池中的线程并没有全部结束返回false,InterruptedException 这个异常也会导致方法的终止,也就是说这个方法只有验证是否关闭的作用,并不会对线程池的状态有影响
  • isShutDown()当调用shutdown()方法后返回为true,但是线程池并不一定停止了,只判断是否接收了命令
  • isTerminated()当线程池确实停止了才会返回true

线程池的拒绝时机和四种拒绝策略

1583892123133.png

拒绝时机

  • 当线程池被shutdown时,拒绝接受任何新任务
  • 当线程池已经达到最大线程数,并且工作队列已满

拒绝策略

  • AbortPolicy线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常
  • DiscardPolicy丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
  • DiscardOldestPolicy丢弃队列最前面的任务,然后重新提交被拒绝的任务(喜新厌旧的策略)
  • CallerRunsPolicy由发起请求的线程处理该任务 (不仅任务得到了执行,还让请求的队列忙于任务不再发出新请求,缓解线程池压力)

合理利用钩子方法可以实现暂停,恢复,日志等功能

线程池的组成部分

  • 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务
  • 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务
  • 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等
  • 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

辨析Executor,Executors,ExecutorService

Executor是顶层接口,能执行我们的线程任务;

Executors工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。

ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法(如shutdown),我们能够获得任务执行的状态并且可以获取任务的返回值。
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;然后ThreadPoolExecutor继承了类AbstractExecutorService,并提供了一些新功能,比如获取核心线程数、获取任务队列等。

1583893284293.png

线程池的五种状态

  • RUNNING

状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

  • SHUTDOWN

状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

  • STOP

状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

  • TIDYING

状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

  • TERMINATED

状态说明:线程池彻底终止,就变成TERMINATED状态。 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

1583893747183.png

线程池实现任务复用的原理

线程重用的核心是,线程池对Thread做了包装,不重复调用hread.start(),而是自己有一个Runnable.run(),run方法里面循环在跑,跑的过程中不断检查我们是否有新加入的子Runnable对象,有新的Runnable进来的话就调一下我们的run(),其实就一个大run()把其它小run()#1,run()#2,...给串联起来了。同一个Thread可以执行不同的Runnable,主要原因是线程池把线程和Runnable通过BlockingQueue给解耦了,线程可以从BlockingQueue中不断获取新的任务。


Terence
4 声望0 粉丝