JAVA 线程池

62 篇文章 3 订阅

线程池

线程池介绍

软件中的"池"可以理解为计划经济

如果不使用线程池,每个任务都新开一个线程处理:例如使用for循环创建线程,当任务数量上升到1000时开销就会很大了(如报内存不足异常),我们希望有固定数量的线程来执行这1000个线程,避免反复创建并销毁线程带来的开销问题

使用线程池解决:反复创建线程开销大;过多的线程会占用太多内存

解决思路:使用少量线程避免内存占用过多;让这部分线程都保持工作,且可以反复执行任务,避免生命周期的损耗

线程池的好处:加快相应速度;合理利用CPU和内存;统一管理资源;

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

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

创建和停止线程池

线程池构造方法的参数

在这里插入图片描述

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

最大量maxPoolSize - 在核心线程的基础上,额外增加的线程数的上限

在这里插入图片描述

添加线程规则

如果线程数小于corePoolSize,创建一个新线程来运行任务

如果等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列

如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程

如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝

在这里插入图片描述

增减线程的特点

  1. 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
  2. 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它
  3. 通过设置maximumPoolSize为很高的值,可以运行线程池容纳任意数量的并发任务
  4. 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize

KeepAliveTime

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

ThreadFactory 用来创建线程

默认使用Executors.defaultThreadFactory()

创建出来的线程都在同一个线程组

如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否守护线程等

工作队列

常见的队列类型:

  1. 直接交接:SynchronousQueue

  2. 无界队列:LinkedblockingQueue

  3. 有界的队列:ArrayblockingQueue

  4. DelayedWorkQueue 延迟队列

线程池应该手动创建还是自动创建

手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险

自动创建线程池(即直接调用JDK封装好的构造方法)可能带来哪些问题?

newFixedThreadPool()

容易造成大量内存占用,可能会导致OOM内存错误

在这里插入图片描述

newSingleThreadExecutor()

原理和1类似,只是线程最大数量为1,当请求堆积时仍可能会占用大量的内存

newCachedThreadPool()

可缓存线程池 特点:具有自动回收多余线程的功能

在这里插入图片描述

弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建非常多的线程,甚至导致OOM

newScheduledThreadPool()

支持定时及周期性任务执行的线程池

以上4种线程池的构造方法的参数

在这里插入图片描述

阻塞队列分析

FixedThreadPool:LinkedblockingQueue无界

CachedThreadPool:SynchronousQueue直接交接

ScheduledThreadPool:DelayedWorkQueue延迟队列

workStealingPool是JDK1.8加入的

和之前的线程池有很大不同

子任务:当任务可以产生子任务适合这种场景,比如处理树、矩阵等

窃取:当一个线程有子任务,其他线程空闲可以帮忙执行其它线程的子任务,特点是为了提高效率任务不能有枷锁,不保证执行顺序;使用递归任务适合

正确的创建线程池的方法

根据不同的业务场景,设置线程池参数

比如:内存有多大,给线程取什么名字等等

线程池数量

线程池里的数量设定为多少比较合适

CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍左右

耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数的很多倍

参考Brain Goetz推荐的计算方法:

线程数=CPU核心数*(1+平均等待时间/平均工作时间)

停止线程池的方法

  1. shutdown 关闭线程池,但运行这个方法时线程池不一定被停止,只是执行关闭任务,要先等待线程池中的任务完成,执行shutdown后再有新任务提交将被拒绝

  2. isShutdown 返回布尔值判断线程是否停止(返回线程停止状态,但仍有可能有在运行的线程)

  3. isTerminated 返回线程是否完全停止!

  4. awaitTermination 测试传入的一段时间内线程是否完全停止

  5. shutdownNow 立刻停止线程,通知正在执行的线程中断,让在队列中等待的线程列表返回List<Runnable>

任务太多,怎么拒绝?

拒绝时机

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

在这里插入图片描述

4种拒绝策略

AbortPolicy 抛出异常

DiscardPolicy 丢弃,不会通知任务

DiscardOldestPolicy 丢弃最老的任务,存放时间最久的

CallerRunsPolicy 让提交任务的线程去执行任务

钩子方法,给线程池加点料

每个任务执行前后;暂停、恢复方法

可以进行日志、统计

实现原理、源码分析

线程池组成部分:
  1. 线程池管理器
  2. 工作线程
  3. 任务队列
  4. 任务接口(Task)
Executor家族

在这里插入图片描述

线程池状态

在这里插入图片描述

使用线程池的注意点

避免任务堆积

避免线程数过度增加

排查线程泄露

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摘星喵Pro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值