一、线程池
1.1、为什么需要线程池
10年前单核CPU电脑,假的多线程,像马戏团小丑玩多个球,CPU需要来回切换。现在是多核电脑,多个线程各自跑在独立的CPU上,不用切换效率高。
1.2、优势
线程池做的主要工作是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,那么超出数量的线程将排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
1.3、主要特点
线程复用、控制最大并发数、管理线程。
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗;
(2)提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行;
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
1.4、架构说明
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类,如下所示:
1.5、常见的线程池
1.5.1、一池N线程
适用场景:执行长期任务性能好,创建一个线程池,一池有N个固定数量的线程。
说明:newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue
1.5.2、一池一线程
适用场景:一个任务一个任务的执行,一池一线程。
说明:newSingleThreadExecutor 创建的线程池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue
1.5.3、可扩容线程
适用场景:执行很多短期异步任务,线程池根据需要创建新线程,可扩容,遇强则强。
说明:newCachedThreadPool创建的线程池将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
1.6、 线程池案例
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/21 8:29* @Description: 一个银行网点有10个业务办理窗口,周末业务不繁忙开2个窗口,业务办理等待区最大能容纳20个人,模拟30个人去银行网点办理业务,使用线程池处理*/
public class ThreadPoolMainApp {public static void main(String[] args) {ExecutorService pool = new ThreadPoolExecutor(2,10,2L,TimeUnit.SECONDS,new ArrayBlockingQueue<>(20),Executors.defaultThreadFactory(),// new ThreadPoolExecutor.AbortPolicy// new ThreadPoolExecutor.CallerRunsPolicy// new ThreadPoolExecutor.DiscardOldestPolicynew ThreadPoolExecutor.DiscardOldestPolicy());// 30个顾客请求try {for (int i = 1; i <= 30; i++) {pool.execute(() -> {System.out.println(Thread.currentThread().getName() + "\t办理业务!");});}} catch (Exception e) {e.printStackTrace();} finally {pool.shutdown();}}}
1.7、ThreadPoolExecutor的底层原理
1.8、线程池7大参数
1.9、线程池的底层工作原理
执行步骤:
第一步:创建了线程池后,开始等待请求;
第二步:当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
a、如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b、如果正在运行的线程数量大于或者等于corePoolSize,那么将这个任务放入队列;
c、如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
d、如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行;
第三步:当一个线程完成任务时,它会从队列中取下一个任务来执行;
第四步:当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
a、如果当前运行的线程数大于corePoolSize,那么这个线程就会被线程池回收;
b、线程池的所有任务完成后,它最终会收缩到corePoolSize的大小;
1.10、线程池的拒绝策略
1.10.1、概述
等待队列已经排满了,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务,这个是时候我们就需要拒绝策略机制合理的处理这个问题。
1.10.2、JDK内置的拒绝策略
AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。
CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中尝试再次提交当前任务。
DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。
说明:以上内置拒绝策略均实现了RejectedExecutionHandle接口
1.11、工作中单一的|固定数的|可变的三种创建线程池的方法哪个用的最多(超级大坑)
答:一个都不用,我们工作中只使用自定义的。
Executors中JDK已经给你提供了,为什么不用?
1.12、工作中如何使用线程池?是否自定义过线程池
/*** @Author : 一叶浮萍归大海* @Date: 2023/11/21 8:29* @Description: 一个银行网点有10个业务办理窗口,周末业务不繁忙开2个窗口,业务办理等待区最大能容纳20个人,模拟30个人去银行网点办理业务,使用线程池处理*/
public class ThreadPoolMainApp {public static void main(String[] args) {ExecutorService pool = new ThreadPoolExecutor(2,10,2L,TimeUnit.SECONDS,new ArrayBlockingQueue<>(20),Executors.defaultThreadFactory(),// new ThreadPoolExecutor.AbortPolicy// new ThreadPoolExecutor.CallerRunsPolicy// new ThreadPoolExecutor.DiscardOldestPolicynew ThreadPoolExecutor.DiscardOldestPolicy());// 30个顾客请求try {for (int i = 1; i <= 30; i++) {pool.execute(() -> {System.out.println(Thread.currentThread().getName() + "\t办理业务!");});}} catch (Exception e) {e.printStackTrace();} finally {pool.shutdown();}}}