线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。
因为频繁的开启线程或者停止线程,线程需要从新被 cpu 从就绪到运行状态调度,需要发生cpu 的上下文切换,效率非常低。
新用户注册:用户注册成功后,异步的去发短信通知、邮件通知、发送优惠券
实际开发项目中 禁止自己 new 线程。
必须使用线程池来维护和创建线程。
核心点:复用机制 提前创建好固定的线程一直在运行状态 实现复用限制线程创建数量。
Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool();可定长度 限制最大线程数
Executors.newScheduledThreadPool() ; 可定时
Executors.newSingleThreadExecutor(); 单例
底层都是基于 ThreadPoolExecutor 构造函数封装
本质思想:创建一个线程,不会立马停止或者销毁而是一直实现复用。
简单模拟手写 Java 线程池:
public class MyExecutors {
public BlockingDeque<Runnable> runnables;
private volatile Boolean isRun = true;
/**
* dequeSize 缓存任务大小
*
* @param dequeSize
* @param threadCount 复用线程池
*/
public MyExecutors(int dequeSize, int threadCount) {
runnables = new LinkedBlockingDeque<Runnable>(dequeSize);
for (int i = 0; i < threadCount; i++) {
WorkThread workThread = new WorkThread();
workThread.start();
}
}
public void execute(Runnable runnable) {
runnables.offer(runnable);
}
class WorkThread extends Thread {
@Override
public void run() {
while (isRun||runnables.size()>0) {
Runnable runnable = runnables.poll();
if (runnable != null) {
runnable.run();
}
}
}
}
public static void main(String[] args) {
MyExecutors myExecutors = new MyExecutors(10, 2);
for (int i = 0; i < 10; i++) {
final int finalI = i; myExecutors.execute(
new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":," + finalI);
}
});
}
myExecutors.isRun=false;
}
}
corePoolSize:核心线程数量 一直正在保持运行的线程
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超出 corePoolSize 后创建的线程的存活时间。
unit:keepAliveTime 的时间单位。
workQueue:任务队列,用于保存待执行的任务。
threadFactory:线程池内部创建线程所用的工厂。
handler:任务无法执行时的处理器。
不会
例如:配置核心线程数 corePoolSize 为 2 、最大线程数 maximumPoolSize 为5
我们可以通过配置超出 corePoolSize 核心线程数后创建的线程的存活时间例如为60s
在 60s 内没有核心线程一直没有任务执行,则会停止该线程。
因为默认的 Executors 线程池底层是基于 ThreadPoolExecutor
构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生内存溢出,会导致我们最大线程数会失效。
如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。
可以自定义异拒绝异常,将该任务缓存到 redis、本地文件、mysql 中后期项目启动实现补偿。
1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
5.实现 RejectedExecutionHandler 接口,可自定义处理器
自定义线程池就需要我们自己配置最大线程数 maximumPoolSize,为了高效的并发运行,当然这个不能随便设置。这时需要看我们的业务是 IO 密集型还是 CPU 密集型。
CPU 密集型
CPU 密集的意思是该任务需要大量的运算,而没有阻塞,CPU 一直全速运行。CPU密集任务只有在真正的多核 CPU 上才可能得到加速(通过多线程),而在单核CPU 上,无论你开几个模拟的多线程该任务都不可能得到加速,因为 CPU 总的运算能力就那些。
CPU 密集型任务配置尽可能少的线程数量:以保证每个 CPU 高效的运行一个线程。一般公式:(CPU 核数+1)个 线程的线程池
IO 密集型
I0 密集型,即该任务需要大量的 IO,即大量的阻塞。在单线程上运行I0 密集型的任务会导致浪费大量的 CPU 运算能力浪费在等待。
所以在 IO 密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
I0 密集型时,大部分线程都阻寒,故需要多配置线程数:
公式:
CPU 核数 * 2
CPU 核数 / (1 - 阻塞系数) 阻塞系数 在 0.8~0.9 之间
查看 CPU 核数:
System.out.println(Runtime.getRuntime().availableProcessors());