线程池简介

一、为什么需要创建线程池

  • 反复创建线程开销大
  • 过多的线程会占用太多的内存

二、线程池的好处

  • 加快响应速度
  • 合理利用CPU和内存
  • 统一管理资源

三、线程池适合应用的场合

  • 服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁数,提高服务器的工作效率

  • 实际上,在开发中如果需要创建5个以上的线程,那么就可以使用线程池来管理

 四、创建线程

1.线程池构造函数的参数

参数名        类型含义
corePoolSizeint核心线程数
maxPoolSizeint最大线程数
keepAliveTimelong保持存活时间
workQueueBlockingQueue任务存储队列
threadFactoryThreadFactory当线程池需要新的线程时,会使用threadFactory来生成新的线程
HandlerRejectedExecutionHandler由于线程无法接受你所提交的任务的拒绝策略

corePoolSize和maxPoolSize

corePoolSize:核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务来时,再创建新线程去执行任务

maxPoolSize:线程池可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程有一个上限,这就是最大量的maxPoolSize

1. 线程添加规则

  • 如果线程数小于corePoolSize,即使其他线程处于空闲状态也会创建一个新线程来运行新任务
  • 如果线程数等于(或大于)corePoolSize但少于maxPoolSize,则将任务放入队列
  • 如果队列(workQueue)已满并且线程数小于maxPoolSize,则创建一个新线程来运行任务
  • 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝(Handler)该任务

2. 增减线程的特点

  • 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
  • 线程希望保持较少的线程数,并且在负载变得很大的时才增加
  • 通过设置maximumPoolSize为很高的值,例如 Integer.MAX_VALUE ,可以允许线程池容纳任意数量的并发任务
  • 是只有在队列填满时才创建多于coolPoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize

keepAliveTime

如果线程池当前的线程数多于corePoolSize,那么多余的线程空闲时间超过keepAliveTime,他们就会被终止

ThreadFactory

新的线程时由ThreadFactory创建的,默认使用Executors.defaultThreadFactory()创建出来的线程都在同一个线程组,拥有同样的NORM_PRIOPITY优先级(优先级是5)并且都不是守护线程。如果自己制定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程

workQueue(工作队列)

  • 直接交接: SynchronousQueue 这种队列是没有容量的,如果要使用这种队列需要将maxPoolSize设置的大一点

  • 无界队列:LinkedBlockingQueue 这种对列是不会被塞满的,如果内存的处理速度跟不上对列存放的速度可能会造成oom异常
  • 有界队列:ArrayBlockingQueue

线程池种类介绍

 newFixedThreadPool

newFixedThreadPool源码如下:

从上面的源码可以看出核心线程数和最大线程数相等,存活时间为0 ,LinkeBlockingQueue是没有容量上限的,所以当请求数越来越多时,并且无法及时处理完毕时也就是请求堆积时会容易造成占用大量的内存,可能会导致OOM。

newSingleThreadPool

  • 单线线程池:它只会用唯一的工作线程来执行任务

 从上面的源码可以看出核心线程数和最大线程数都为1,存活时间为0 ,LinkeBlockingQueue是没有容量上限的,所以这也会导致请求堆积时,可能会占用大量的内存。

newCachedThreadPool

  • 可缓存线程池
  • 无界线程池,具有自动回收多余线程的功能

从上面的源码可以看出核心线程数为0,最大的线程数被设置为Integer.MAX_VALUE,这可能会创建数量非常多的线程甚至导致OOM

newScheduledThreadPool

  • 支持定时及周期性任务执行的线程池,调用方法如下图

WokrStaelingPool

  • 子任务:任务可能会产生子任务
  • 窃取:假设有三个线程,第一个线程会产生它自己独有的子任务,会将自己的子任务放在阻塞队列中,剩下的两个线程处于空闲状态时会将第一个线程阻塞队列中的任务取出来帮助第一个线程执行任务
  • 任务执行顺序不能保证

正确的创建线程的方法

  • 根据不同的业务场景,自己设置线程池参数,例如:内存的大小,线程的名称
  • CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍
  • 耗时IO型(读写数据库、文件、网略读写等):最佳线程一般会大于cpu核心数很多倍
  • 线程数=CPU核心数 *(1+平均等待时间/平均工作时间)

停止线程池的正确方法

  • shutdown:执行这个方法后线程池并不一定会停止,这个方法仅仅是初始化整个关闭过程,线程池会将正在执行的任务和等待执行的任务执行完毕后再关闭,再有新任务提交后会被拒绝
  • isShutDown:判断是否进入停止状态
  • isTerminated: 整个程序不仅是开始停止并且所有任务执行完毕
  • awaitTermination: 等待一段时间后整个线程执行完毕会返回true,没执行完毕会返回false
  • shutdownNow:立刻关闭线程池,会中断正在执行的线程,会直接返回等待的线程

线程池拒绝

拒绝时机

  • 当Executor关闭时,提交新任务会被拒绝
  • 当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时

拒绝策略

  • AbortPolicy:直接抛出异常
  • DiscardPolicy:丢弃无法处理的任务
  • DiscardOldestPolicy:丢弃队列中存放时间最久的任务
  • CallerRunsPolicy :如果无法处理任务就由任务提交者去执行,可以避免损失

钩子方法

可以在线程池执行之前或者执行之后做一些处理

protected void beforeExecute(Thread t, Runnable r) { } // 任务执行前
protected void afterExecute(Runnable r, Throwable t) { } // 任务执行后
protected void terminated() { } // 线程池执行结束后

线程池组成部分

  • 线程池管理器
  • 工作线程
  • 任务队列
  • 任务接口(Task)

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

相同线程执行不同任务

当给线程池提交任务时会调用 execute(),如下图所示

execute() 内部会添加一个worker(addWork()),也会把任务传进去,如下图所示

线程启动的时候我们将内部类worker对象传入进去了,内部类Worker是实现了runable接口的,jvm执行run方法的时候就会执行Worker中的run方法,run方法内部调用了runWorker(),runWorker 里面一个while循环,当我们的task不为空的时候它就永远在循环,并且会源源不断的从getTask()获取新的任务

 getTask()方法是从队列中获取任务workQueue

线程池状态

  • RUNNING:接受新任务并处理排队任务
  • SHUTDWON:不接受新任务,但处理排队任务
  • STOP:不接受新任务也不处理排队任务,并中断正在进行的任务
  • TIDYING:所有任务都已终止,workCount = 0,线程就会转换到TIDYING状态,并将运行terminate()钩子方法
  • TERMINATED:terminate() 运行完成

使用线程池的注意点

  • 避免任务堆积
  • 避免线程数过度增加
  • 排查线程泄露
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值