线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。
因为频繁的开启线程或者停止,线程需要从新被cpu从就绪到运行状态调度,效率非常低。
所以使用线程可以实现复用,从而提高效率。
1.降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
2.提高响应速度:任务到达时,无需等待线程创建即可立即执行。
3.提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
4.提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool();可定长度
Executors.newScheduledThreadPool() ; 可定时
Executors.newSingleThreadExecutor(); 单例
底层都是基于ThreadPoolExecutor构造函数封装
1.ArrayBlockingQueue:
有界队列,基于数组结构,按照队列FIFO原则对元素排序;
2.LinkedBlockingQueue:
无界队列,基于链表结构,按照队列FIFO原则对元素排序,Executors.newFixedThreadPool()使用了这个队列; 无界默认是Integer.MAX_VALUE,有界则是 可以自己定义
3.SynchronousQueue:
同步队列,该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直被阻塞,Executors.newCachedThreadPool()使用了这个队列;
4.PriorityBlockingQueue:
优先级队列,具有优先级的无限阻塞队列。
一个核心线程任务集合,一个缓存线程队列,核心线程任务处于一直运行状态,核心线程里面的任务从缓存队列里面取,核心线程用完来不及处理的就放在缓存线程任务中排队等候处理,通过构造方法初始化核心线程集合大小和缓存队列大小
package com.example.demo.mall.threadPool; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class MyExecutor { /*** * 存放核心线程数 */ private List<TaskThread> taskThreads; /** * 缓存队列 */ private BlockingQueue<Runnable> blockingQueues; public MyExecutor(int codeThreads, int threadQueues) { taskThreads = new ArrayList<TaskThread>(codeThreads); this.blockingQueues = new LinkedBlockingQueue<Runnable>(threadQueues); for (int i = 0; i < codeThreads; i++) { TaskThread taskThread = new TaskThread(); taskThread.start(); taskThreads.add(taskThread); } } public MyExecutor(int codeThreads, BlockingQueue<Runnable> blockingQueues) { taskThreads = new ArrayList<TaskThread>(codeThreads); this.blockingQueues = blockingQueues; for (int i = 0; i < codeThreads; i++) { TaskThread taskThread = new TaskThread(); taskThread.start(); taskThreads.add(taskThread); } } class TaskThread extends Thread { @Override public void run() { while (true) { Runnable runnable = blockingQueues.poll(); if (runnable != null) { runnable.run(); } } } } public boolean executor(Runnable runnable) { return blockingQueues.offer(runnable); } public boolean executor2() { Runnable runnable = blockingQueues.poll(); return blockingQueues.offer(runnable); } public static void main(String[] args) throws InterruptedException { MyExecutor myExecutor = new MyExecutor(2, 2); Thread t1 = new Thread(() -> { System.out.println("t1线程运行"); }); Thread t2 = new Thread(() -> { System.out.println("t2线程运行"); }); Thread t3 = new Thread(() -> { System.out.println("t3线程运行"); }); BlockingQueue<Runnable> queues = new LinkedBlockingQueue<Runnable>(3); queues.put(t1); queues.put(t2); queues.put(t3); myExecutor.executor(t1); MyExecutor myExecutor2 = new MyExecutor(2, queues); myExecutor2.executor2(); // for (int i = 0; i < 10; i++) { // int finalI = i; // boolean executor = myExecutor.executor(() -> { // System.out.println(Thread.currentThread().getName() + "," + finalI); // }); // } } }
corePoolSize:核心线程数量 一直正在保持运行的线程
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超出corePoolSize后创建的线程的存活时间。
unit:keepAliveTime的时间单位。
workQueue:任务队列,用于保存待执行的任务。
threadFactory:线程池内部创建线程所用的工厂。
handler:任务无法执行时的处理器。
1.RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理。
2.SHUTDOWN:线程池不可以接受新任务,但是可以对已添加的任务进行处理。
3.STOP:线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
4.TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行构造函数5.terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
6.TERMINATED:线程池彻底终止的状态。
核心原理:
提交任务的时候比较核心线程数,如果当前任务数量小于核心线程数的情况下,则直接复用线程执行。如果任务量大于核心线程数,则缓存到队列中。如果缓存队列满了,且任务数小于最大线程数的情况下,则创建线程执行。如果队列且最大线程数都满的情况下,则走拒绝策略。注意:最大线程数,在一定时间没有执行任务 则销毁避免浪费cpu内存。
专业术语。
1.当线程数小于核心线程数时,创建线程。
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3.当线程数大于等于核心线程数,且任务队列已满
3.1若线程数小于最大线程数,创建线程
3.2若线程数等于最大线程数,抛出异常,拒绝任务
如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。
可以自定义异拒绝异常
rejectedExecutionHandler:任务拒绝处理器 两种情况会拒绝处理任务: 1.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务 2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。 ThreadPoolExecutor类有几个内部实现类来处理拒绝任务: 1.AbortPolicy 丢弃任务,抛运行时异常 2.CallerRunsPolicy 执行任务 3.DiscardPolicy 忽视,什么都不会发生 4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务 5.实现RejectedExecutionHandler接口,可自定义处理器
具体可以根据cpu密集与io密集配置
CPU密集,线程不会阻塞,一直在运行,线程代码非常快结束,则最大线程数配置与
Cpu核数相当就可以了。
Io密集,比如读取io、导致当前线程有可能阻塞,或者线程执行代码非常耗时,则最大线程数配置为Cpu核数*2即可。
为什么阿里不建议使用Executors
阿里巴巴官方技术手册推荐最好使用
ThreadPoolExecutor自己定义
为什么需要开发者自己基于ThreadPoolExecutor构造函数封装?
答案:
因为底层都是基于无界队列实现, 缓存线程队列可以无限的存放容量大小,有可能会内存溢出。线程池中最大线程数是无效的。 ThreadPoolExecutor构造函数有那些参数 1.corePoolSize 核心线程数 一直正在运行的线程 2.maximumPoolSize 最大线程数 A.满足条件 队列满的情况下 最大线程数组成:核心线程数+非核心线程数=最大线程数 corePoolSize =10 maximumPoolSize 15 非核心线程数=15-10; keepAliveTime 超时时间 60s 在一定时间内没有线程使用 非核心线程数的情况下,则销毁,节约cpu的资源。如果我们线程一直在运行状态存在那些缺陷:非常消耗cpu资源 BlockingQueue<Runnable> workQueue 缓存线程队列。Handler 自定义封装拒绝策略回调方法 ThreadPoolExecutor底层原理解读 核心线程数10最大线程数15 只有缓存队列有界的最大线程数才有用,超出缓存队列就再开启一个线程开始消费,但开启的数目累计最多不能超过最大线程数 缓存队列无界默认 Integer最大值 2的32次方 提交线程任务数(1-15)<核心线程数10 直接使用核心线程数复用。 任务的id1-10 直接使用核心线程数复用。提交线程任务数(10-15)>核心线程数10 当前我们核心线程没有足够的线程运行的情况下,超过5任务id直接缓存到队列中。如果队列没有满的情况下,都是我们核心线程数一直在复用。如果队列满了的情况下,非核心线程数: 最大线程数-核心线程数= 线程池同时最大容量:最大线程数+缓存队列 拒绝策略:提交线程任务数>最大线程数+缓存队列 =拒绝策略。因为我们java中自己封装的线程:都是采用无界队列, 队列如果没有满的情况下,非核心线程数不会开启,也不会走拒绝策略。 这样的话有可能会内存溢出。 最大线程数一定要比核心线程数:计算出非核心线程数 最大线程数=核心线程数+最大线程数 非核心线程数超时时间:规定时间内,如果没有线程任务使用 非核心线程数的情况下,则直接会销毁掉,节约服务器的内存。 线程池拒绝线程任务:>最大线程数+队列缓存容量 采用异常捕获 将拒绝的线程任务记录到日志表中后期做定时任务补偿。重点:各个参数和思考用有界队列存放缓存线程任务,还有5重拒绝策略
每一行代码都有它的涵义,多问一句为什么;别怕,理清思路,一切代码都是数据的流动和转化,耐心一点,慢慢积累!一起加油!!!