使用线程池的优势
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行.
他的主要特点为:线程复用:控制最大并发数:管理线程.
优点为:
- 第一:降低资源消耗.通过重复利用自己创建的线程降低线程创建和销毁造成的消耗.
- 第二:提高响应速度.当任务到达时,任务可以不需要等到线程和粗昂就爱你就能立即执行.
- 第三: 提高线程的可管理性.线程是稀缺资源,如果无限的创阿金,不仅会消耗资源,还会较低系统的稳 定性,使用线程池可以进行统一分配,调优和监控。
线程池如何进行使用
Java线程池是通过Executor框架实现的,该框架中用到了
Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类.
具体的继承结构为,核心使用ThreadPoolExecutor类进行构建
创建常见的线程池new的三种方式
固定线程数的线程池、单线程化的线程池和灵活扩张的线程池(但是实际都不使用,实际中使用自定义线程池)
三者的底层都是ThreadPoolExcutor类
Excutors.newFixedThreadPool(int) -----通常用于执行一个长期的任务,性能较好
主要特点如下:
1.创建一个定长线程池,可控制线程的最大并发数,超出的线程会在队列中等待.
2.newFixedThreadPool建的线程池corePoolSize和MaxmumPoolSize是 相等的,
它使用的的LinkedBlockingQueue
Excutors.newSingleThreadPool() -------一个任务一个线程进行执行
主要特点如下:
1.
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
保证所有任务都按照指定顺序执行.
2.
newSingleThreadExecutor将corePoolSize和MaxmumPoolSize都设置为1,
它使用的的LinkedBlockingQueue
Excutors.newCachedThreadPool() -----使用于短期异步的小程序
主要特点如下:
1.
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程.
2.
newCachedThreadPool将corePoolSize设置为0MaxmumPoolSize设置为Integer.MAX_VALUE,
它使用的是SynchronousQUeue,也就是说来了任务就创建线程运行,如果线程空闲超过60秒,就销毁线程
线程池的七大参数介绍
用银行完成业务的流程来类比
- corePoolSize :核心线程数(类比于银行的值班柜台–2)
- maxmumPoolSize :最大线程数(类比于银行一共有几个柜台–5)
- keepAliveTime :一个非核心线程空闲的时间,超时就销毁非核心线程(加班的柜台,一个小时没有业务就下班)
- unit :上面一个参数的单位
- workQueue:任务队列,一般是阻塞队列,用于存放提交但没有来得及执行的任务(类似于银行等 待区的客户)
- **threadFactory:**表示生成线程池中线程的工厂,一般使用默认(内部封装了线程生成的细节和标准)
- handler:拒绝策略,当线程达到最大数,同时任务队列满了的时候,对后面来的任务采取的拒绝的措施。
线程的底层工作原理
线程池参数的设计
拒绝策略
JDK内置有四种拒绝策略:
1. AborPolicy(默认):会直接抛出RejectedException异常阻止系统正常运行
2. CallerRunPolicy:该策略不会抛异常也不会抛弃任务,而是将其返回到原本的那个线程
3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然 后把当前任务加入队列中尝试再次提交
4. DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常.如果允许任务丢失,这是最好的拒绝策略
在进行线程池创建选择时,一般都是使用自定义线程池(阿里巴巴手册中规范的)
参考阿里巴巴java开发手册
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,
解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,
可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,
可能会创建大量的线程,从而导致OOM
自定义线程池简单实现,七大参数的选择
- corePoolSize :2
- maxmumPoolSize :5
- keepAliveTime :1
- unit :seconds
- workQueue:LinkedBlockingDeque
- threadFactory: 默认工厂
- handler:拒绝策略,四种自选(DiscardPolicy)
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(3),
Executors.defaultThreadFactory(),
//默认抛出异常
//new ThreadPoolExecutor.AbortPolicy()
//回退调用者
//new ThreadPoolExecutor.CallerRunsPolicy()
//处理不来的不处理
//new ThreadPoolExecutor.DiscardOldestPolicy()
new ThreadPoolExecutor.DiscardPolicy()
);
//模拟10个用户来办理业务 没有用户就是来自外部的请求线程.
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
//threadPoolInit();
}
合理配置线程池的考虑
-
CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。 CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程), 而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速, 因为CPU总的运算能力就那些。CPU密集型任务配置尽可能少的线程数量: 一般公式: CPU核数+1个线程的线程池
-
IO密集型
I0密集型,即该任务需要大量的IO,即大量的阻塞。 在单线程上运行|0密集型的任务会导致浪费大量的CPU运算能力浪费在等待。 所以在I0密集型任务中使用多线程可以大大的加速程序运行, 即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。 一般公式:核数*2