线程池
线程池优势
- 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,
- 使用线程池可以进行统一的分配,调优和监控。
线程池的创建
Java 通过Executors 来提供四种创建线程池的方法:
- Executors.newFixThreadPool(n) : 创建指定个数的线程池
- Executors.newSingleThreadExecutor: 创建只有一个线程的线程池
- Executors.newCacheThreadPool: 适用于不定个数的短期任务的执行,通过内部的调度,根据实际的情况动态地调整线程池中的线程数量
接下来,让我们看一下部分底层创建源码,可以发现,源码都是使用ThreadPoolExecutor 来创建的。
<code>public
static
ExecutorServicenewFixedThreadPool
(
int
nThreads) {return
new
ThreadPoolExecutor(nThreads, nThreads,0L
, TimeUnit.MILLISECONDS,new
LinkedBlockingQueue()); }public
static
ExecutorServicenewCachedThreadPool
()
{return
new
ThreadPoolExecutor(0
, Integer.MAX_VALUE,60L
, TimeUnit.SECONDS,new
SynchronousQueue()); }public
static
ExecutorServicenewSingleThreadExecutor
()
{return
new
FinalizableDelegatedExecutorService (new
ThreadPoolExecutor(1
,1
,0L
, TimeUnit.MILLISECONDS,new
LinkedBlockingQueue())); } /<code>
其实,我们可以直接使用ThreadPollExecutor 来创建线程池,这也是阿里巴巴所推荐的,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
在《Java开发手册》中,阐述了直接使用Executors 的两大弊端:
- FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 - CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
使用ThreadPoolExecutor 创建线程池(阿里推荐)
首先来看一下ThreadPoolExecutor 的各项参数
<code>public
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue 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; } /<code>
七大参数
- corePoolSize: 线程池中常驻核心线程数
- maximumPoolSize: 线程池能够容纳同时执行的最大线程数,此值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间,当前的线程池的数量超过corePoolSize 时,当空闲时间达到keepAliveTime 值时,多余的空闲线程会被销毁直到剩下corePoolSize 个线程为止。
- unit:keepAliveTime 的单位
- workQueue:阻塞任务队列,被提交但尚未被执行的任务(当任务队列满时,线程池会扩充线程数,扩充后的线程数会小于maximumPoolSize)
- threadFactory:表示生成线程的线程工厂,一般使用默认便可
- RejectedExecutionHandler:拒绝策略,当阻塞队列已满而且活跃线程数已经达到了maximumPoolSize 后,会启动拒绝策略,拒绝请求的加入
四大拒绝策略
- AbortPolicy(默认):该策略直接抛出RejectedExecutionException 异常阻止系统的正常运行
- CallerRunsPolicy:该策略不会抛弃任务也不会抛出异常,而是将任务退回到调用者
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中再次提交该任务
- DiscardPolicy:直接丢弃任务,不给予任何的处理,也不抛出异常。
使用ThreadPoolExecutor 创建线程池的例子
<code> ExecutorService executorService =new
ThreadPoolExecutor(2
,5
,5
L, TimeUnit.SECONDS,new
LinkedBlockingQueue(3
), Executors.defaultThreadFactory(),new
ThreadPoolExecutor.AbortPolicy()); /<code>
线程池的工作流程
- 在创建了线程池后,等待提交过来的任务请求
- 当调用了execute() 方法添加了一个请求任务后,线程池会做如下判断:
- 如果正在运行的线程池数量小于corePoolSize,那么马上创建线程运行这个任务
- 如果正在运行的线程数量大于或等于corePoolSize时,那么会将请求任务放到阻塞队列中
- 如果阻塞队列已经满了,而且正在运行的线程数量小于maximumPoolSize,那么会创建非核心线程来立即运行这个任务
- 如果阻塞队列已经满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动拒绝策略
- 当一个线程执行完任务时,会从队列中取出下一个任务出来继续执行
- 当一个线程的空闲时间大于kiveAliveTime 时,会关闭一些线程,直到线程数达到corePoolSize 为止
线程池线程数的合理配置
CPU 密集型
CPU 密集型需要大量的计算,没有阻塞,CPU 一直在全速运行
参考公式:CPU 核数 + 1个线程的线程池
IO 密集型
IO 密集型,需要大量的IO操作,即大量的阻塞 在单线程上运行IO密集型的任务会导致浪费大量CPU运算能力浪费在等待上,所以在IO密集型任务中运行多线程k可以大大加速程序的运行
参考公式:CPU 核数/(1-阻塞系数),阻塞系数在0.8-0.9 之间
Runnable and Callable
- Runnable 没有返回值 - Callable 有返回值
<code>class
MyThread
implements
Runnable
{public
void
run
()
{ } }class
MyThread2
implements
Callable
<Integer
> {public
Integercall
()
{return
123
; } }public
class
Test
{public
static
void
main
(String[] args)
throws
Exception { FutureTask futureTask =new
FutureTask<>(new
MyThread2()); Thread thread =new
Thread(futureTask,"aa"
); thread.start(); System.out.println(futureTask.get()); } }/<code>
专栏
Java面试通关100问
19.9币
241人已购
關鍵字: keepAliveTime maximumPoolSize public