线程池使用及优势:
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等待,等其它线程执行完毕,再从队列中取出任务来执行。
主要特点:线程复用、控制最大并发数、管理线程。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
1.线程池7大参数
(1)corePoolSize :核心线程数,线程池中的常驻核心线程数
在创建线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
当线程池中的线程数目达到corePoolSize后,就会把到达的队列放到缓存队列中
(2)maximumPoolSize :线程池能够容纳同时执行的最大线程数,此值必须大于等于1、
相当有扩容后的线程数,这个线程池能容纳的最多线程数
(3)keepAliveTime :多余的空闲线程存活时间
当线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余的空闲线程会被销毁,直到 只剩下corePoolSize个线程为止
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
(4)unit : keepAliveTime的单位
(5)workQueue :任务队列,被提交的但未被执行的任务(类似于银行里面的候客区)
LinkedBlockingQueue:链表阻塞队列
SynchronousBlockingQueue:同步阻塞队列
(6)threadFactory : 表示生成线程池中工作线程的线程工厂,用于创建线程池 一般用默认即可 (7)handler : 拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数(maximumPoolSize3)时,如何来拒绝请求执行的Runnable的策略
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;
}
2.线程池4个常用方式
(1)定长线程池 -> Executors.newFixedThreadPool(int) :
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(2)单线程池 -> Executors.newSingleThreadExecutor() :
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(3)可缓存线程池 -> Executors.newCachedThreadPool() :
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(4)调度线程池 -> Executors.newScheduledThreadPool(int) :
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
3.线程池底层工作原理
4.线程池的4种拒绝策略
以下所有拒绝策略都实现了RejectedExecutionHandler接口
-
AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
-
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案
-
CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
-
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
5.线程池实际使用
推荐使用自定义线程池 ThreadPoolExecutor(·······)
根据阿里巴巴手册:并发控制这章
-
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
-
使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题,如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
-
-
线程池不允许使用Executors去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
-
Executors返回的线程池对象弊端如下:
-
FixedThreadPool和SingleThreadPool:
-
允许的请求队列长度为:Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
-
-
CacheThreadPool和ScheduledThreadPool
-
允许的创建线程数量为:Integer.MAX_VALUE,线程数上限太大导致oom
-
-
-
6.线程池配置合理线程数
生产环境中如何配置 corePoolSize 和 maximumPoolSize
这个是根据具体业务来配置的,分为CPU密集型和IO密集型
(1)CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)
而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些
CPU密集型任务配置尽可能少的线程数量:
一般公式:CPU核数 + 1个线程数
(2)IO密集型
a.CPU核数 * 2
由于IO密集型任务线程并不是一直在执行任务,则可能多的线程,如 CPU核数 * 2
IO密集型,即该任务需要大量的IO操作,即大量的阻塞
在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力花费在等待上
所以IO密集型任务中使用多线程可以大大的加速程序的运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
IO密集时,大部分线程都被阻塞,故需要多配置线(程数:
b.CPU核数 / (1 - 阻塞系数)
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右
例如:8核CPU:8/ (1 - 0.9) = 80个线程数
7.死锁及定位分析
jps -l :定位进程号
jstack :找到死锁查看详情