线程池
11.1 线程池概述
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用
线程池(英语:thread pool)一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度
线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超过数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的特点:
- 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
11.2 线程池架构
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor 这几个类
说明:Executors为工具类,I为接口类,C为实现类
11.3 线程池使用方式
Executors.newFixedThreadPool(int)
:一池N线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
Executors.newSingleThreadExecutor()
:一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
Executors.newCachedThreadPool()
:一池可扩容根据需求创建线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
执行线程:execute()
关闭线程:shutdown()
void execute(Runnable command);
参数为Runnable接口类,可以通过设置lambda
具体案例代码案例
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
ExecutorService threadPool3 = Executors.newCachedThreadPool();
// 是个顾客请求
try{
for (int i = 1; i <= 10; i++) {
// 到此时执行execute()方法才创建线程
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}finally {
// 关闭线程
threadPool1.shutdown();
}
}
}
一池N线程输出结果为:
pool-1-thread-2 办理业务
pool-1-thread-1 办理业务
pool-1-thread-3 办理业务
pool-1-thread-5 办理业务
pool-1-thread-4 办理业务
pool-1-thread-2 办理业务
pool-1-thread-1 办理业务
pool-1-thread-4 办理业务
pool-1-thread-5 办理业务
pool-1-thread-3 办理业务
一池一线程输出结果为:
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
一池可扩容根据需求创建线程输出结果为:
pool-3-thread-1 办理业务
pool-3-thread-5 办理业务
pool-3-thread-4 办理业务
pool-3-thread-7 办理业务
pool-3-thread-2 办理业务
pool-3-thread-3 办理业务
pool-3-thread-8 办理业务
pool-3-thread-6 办理业务
pool-3-thread-9 办理业务
pool-3-thread-10 办理业务
11.4 线程池底层原则
通过查看上面三种方式创建对象的类源代码
都有 new ThreadPoolExecutor
具体查看该类的源代码,涉及七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
// 常驻线程数量(核心)
this.corePoolSize = corePoolSize;
// 最大线程数量
this.maximumPoolSize = maximumPoolSize;
// 阻塞队列(排队的线程放入)
this.workQueue = workQueue;
// 线程存活时间
this.keepAliveTime = unit.toNanos(keepAliveTime);
// 线程工厂,用于创建线程 线程工厂,用于创建线程
this.threadFactory = threadFactory;
// 拒绝测试(线程满了
this.handler = handler;
}
11.5 线程池的七个参数
具体代码中的七个参数讲解:
int corePoolSize,常驻线程数量(核心)
int maximumPoolSize,最大线程数量
long keepAliveTime,TimeUnit unit,线程存活时间
BlockingQueue workQueue,阻塞队列(排队的线程放入)
ThreadFactory threadFactory,线程工厂,用于创建线程
RejectedExecutionHandler handler,拒绝测试(线程满了)
具体工作流程是:
- 在执行创建对象的时候不会创建线程,创建线程的时候execute()才会创建
- 先到常驻线程,满了之后再到阻塞队列进行等待,阻塞队列满了之后,在往外扩容线程,扩容线程不能大于最大线程数。大于最大线程数和阻塞队列之和后,会执行拒绝策略。
- 阻塞队列为3,常驻线程数2,最大线程数5
11.6 线程池底层工作流程
对流程图的解释
现在假设来了9个线程,在执行execute()方法才创建线程。
第1-2个线程进入线程池创建
第3-5个线程进入阻塞队列
第6-8个线程会为他们创建新线程执行(直接运行线程6而非线程3)
第9个线程会被拒绝
总结来说:先到常驻线程,满了之后再到阻塞队列进行等待,阻塞队列满了之后,在往外扩容线程,扩容线程不能大于最大线程数。大于最大线程数和阻塞队列之和后,会执行拒绝策略。
具体的拒绝策略有:
- 抛异常-AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
- 谁调用找谁-CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
- 抛弃最久执行当前-DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中,尝试再次提交当前任务
- 不理不问-Policydiscard:该策略默默地丢弃无法处理的任务,不予任何处理也不抱出异常。如果允许任务丢失,这是最好的一种策略
11.7 自定义线程池
实际在开发中不允许使用Executors创建,而是通过ThreadPoolExecutor的方式,规避资源耗尽风险
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
ExecutorService threadPool = new ThreadPoolExecutor(
// 常驻核心线程
2,
// 最大线程数量
5,
// 线程存活时间
2L,
TimeUnit.SECONDS,
// 阻塞队列
new ArrayBlockingQueue<>(3),
// 线程工厂
Executors.defaultThreadFactory(),
// 拒绝策略
new ThreadPoolExecutor.AbortPolicy()
);
其他都同理,只是调用ThreadPoolExecutor类,自定义参数
public class ThreadPoolTest {
public static void main(String[] args) {
// 组定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
// 常驻线程数量(核心)2个
2,
// 最大线程数量5个
5,
// 线程存活时间:2秒
2L,
TimeUnit.SECONDS,
// 阻塞队列
new ArrayBlockingQueue<>(3),
// 默认线程工厂
Executors.defaultThreadFactory(),
// 拒绝策略。抛出异常
new ThreadPoolExecutor.AbortPolicy()
);
try{
for (int i = 1; i <= 8; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
输出结果
pool-1-thread-1 办理业务
pool-1-thread-4 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-1 办理业务
pool-1-thread-5 办理业务
如果线程数大于 最 大 线 程 数 量 + 阻 塞 队 列 容 量 最大线程数量+阻塞队列容量 最大线程数量+阻塞队列容量 则抛出异常