线程池的基本概念
一.什么是线程池.
ThreadPoolExecutor是Java中的一个线程池执行器,它可以管理和调度多个线程,使得多个任务可以并发执行,从而提高程序的执行效率。线程池执行器可以控制线程的数量、执行方式、优先级等,避免了线程的频繁创建和销毁,从而减少了系统开销。简单来说就是,提前把要用的对象创建好,用完对象不急着释放,留下来以备下一次使用
ThreadPoolExecutor 本身用起来比较复杂.因此标准库还提供了另一个版本,把ThreadPoolExecutor给封装了一下.—>Executors 工厂类.通过这个类创建出不同的线程池对象.(在内部把ThreadPoolExecutor创建好了并且设置了不同的参数)
二.为什么要引入线程池?
为了解决进程频繁的创建销毁所带来的资源消耗而引入线程~~当线程过于频繁的创建和销毁的时候,此时消耗的资源也就不能忽视了,此时解决这个问题的办法有两种:
- 1.引入轻量级线程,也叫"纤程/协程"…协程的本质就是程序员在用户态代码中进行调度,不是靠内核的调度器调度.
- 使用线程池,相当于牺牲了空间提高了效率.
.为什么从线程池获取线程,要比在系统内核中申请创建线程更高效?
基本结论:如果一个工作,自己可以完成,那么一定是更可控,更高效的; 反之,让别人来完成,那么会不可控,更低效.
因此.通过系统申请线程,就需要内核来完成.从线程池中取线程是纯用户态代码,因此从线程池取线程会更加高效.
三.线程池的构造方法(此处只介绍参数最多的那一种)
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
- ①.corePoolSize 核心线程数—>一个线程池中至少有的线程数.
- ②.maximumPoolSize 最大线程数—>一个线程池里最多有几个线程.
- ③.keepAliveTime 除了核心线程,空闲的线程所能存在的最长时间.
- ④.TimeUnit 时间单位
TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒 - ⑤.BlockingQueue 阻塞队列,线程池中可以含有多个任务.
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。 - ⑥.ThreadFactory 线程工厂–>通过这个工厂来创建线程
工厂模式是一种常见的设计模式:通过专门的工厂类/工厂对象来创建线程.==>展开来说就是通过静态方法(也可以是普通方法)来封装new操作在方法内部,通过设定不同的属性来完成对象的创建. - ⑦.RejectedExecutionHandler 拒绝策略–>在一个线程池中有一个阻塞对列,当队列的任务满了以后,需要制定一个拒绝策略.
1.拒绝策略一: 抛出RejectedExecutionException异常.此时新的任务没法进行了,并且旧的任务也没法进行了.满了之后再加,原来的和新加的任务都没办法进行了.
2.拒绝策略二: 新的任务有添加任务的线程执行.新的任务会执行,但是不是线程池执行,而是添加新任务的线程执行.
3.拒绝策略三: 丢弃最老的任务,执行新的任务.新的线程加入执行,把最早加入的线程抛弃
4.拒绝策略四: 无视新的线程,按原来的线程继续执行.新的线程直接无视,按原来的线程执行
四. 线程池中线程数目的设定为多少合适?
不同的程序,需要设置的线程数目是不一样的,需要具体问题具体分析.
- 看一个线程是cpu密集型任务(这个线程大部分时间都在cpu上运行(进行计算~~)),还是io密集型任务(这个线程大部分时间都是在等待IO,不去cpu上执行)
- 如果一个进程中的所有线程都是cpu密集型,每个线程的所有时间都是在cpu上执行,此时,线程池中线程的数目的设定不能超过N(cpu逻辑核心数)
如果一个进程中的所有线程都是IO密集型,每个线程的大部分时间都是在等待IO,cpu消耗很少,此时,线程池中的线程数目的设定可以远远超过N(cpu的逻辑核心数). - 但在实际开发中,很难直接对线程池中的数目进行估算…此时更合适的做法,**就是通过实验/测试找到合适的线程数目.**尝试给线程池设定不同的线程数目,分别进行性能测试.衡量每种线程数目下,总的时间开销,和系统资源占用的开销,找到这两者之间的合适值!!
五.创建线程池的七种方式.
- Executors.newFixedThreadPool():fixed汉语意思是固定的, 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
- Executors.newCachedThreadPool():cache的意思是缓存, 创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- Executors.newSingleThreadExecutor():创建单个线程数的线程池,它可以保证先进先出的执行顺序;
- Executors.newScheduledThreadPool():创建一个定长线程池,支持定时及周期性任务执行
- Executors.newSingleThreadScheduledExecutor():创建一个单线程的支持定时及周期性任务执行的线程池;
- Executors.newWorkStealingPool():创建一个抢占式执行的线程池(任务执行顺序不确定)