创建线程池
线程池创建的好处是减少在创建和销毁线程上所消耗的时间和资源,解决资源不足的问题,通过继承thread类,实现runable接口或者callable接口开启线程,容易造成同类线程的大量创建、从而导致消耗完内存或者过度切换问题。
ThreadPoolExecutor是阿里开发手册和诸多开发者推荐使用创建线程池的,因为可以使初学者对线程池的运行规则更加明确,规避资源耗尽的风险,有些同学,也比较习惯用Executors进行线程池的创建,但是Executors创建返回的线程池对象有如下弊端:
1、fixedThreadPool 和singleThreadPool ,允许的请求队列的长度为Integer.MAX_VALUE,当大量请求来的时候,可能会造成大量请求的堆积,从未导致OOM
2、cachedThreadPool ,允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而造成OOM
名词解析:
fixedThreadPool:定长线程池,工作队列为无边界队列(队列长度Integer.MAX_value),核心线程数和最大线程数一致,FixedThreadPool 是通过 java.util.concurrent.Executors 创建ThreadPoolExecutor 实例 实现的,方法如下
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
因为其可能造成大量请求堆积的弊端,所以适用:大量短生命周期的异步任务(many short-lived asynchronous task)
singleThreadPool:单线程线程池,核心线程数和最大线程数都是1,,工作队列为无边界队列,singleThreadPool是通过java.util.concurrent.Exxcutors创建ThreadPoolExcutor实例实现的,方法如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
因为其只有一个线程去循环处理任务,所以适用于单线程处理任务的场景。
cachedThreadPool:无核心线程,最大线程数相当于无界,有对最大线程开启使用之后,线程的存活时间。队列为无容量队列。
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new synchronousQueue<Runnable>());
}
SynchronousQueue 内部没有容量,一个插入操作总是对应一个移除操作
因为队列没容量,所以适用:稀疏提交任务,任务声明周期较短的任务,提交立马被消费的情况。
scheduledThreadPool:会创建固定长度的线程池,可以延时、定时或周期性的执行任务(此线程池目前接触较少,后续会关注并补充)
线程池的处理逻辑
线程池的处理流程(threadpoolExecutor)
线程池队列
ArrayBlockingQueue:先说结构,数组类型的阻塞队列,创建时必须制定大小,就像定义一个数组一样,任务从队列取出执行时,先进先出(FIFO)。基于上图 9-1线程池处理流程发现,当核心线程数满了,此队列才会起作用,并且在核心线程满的情况下,队列中的任务会一直堆积到初始定义的队列长度,当队列满了,并且(核心线程+新提交的任务数)>线程池最大线程数时,会走线程池创建时设置的处理策略,下部分会讲到。
LinkedBlockingQueue:链表类型的阻塞队列(FIFO),创建时可以不定义链表长度,此时队列为无界队列(Integer.MAX_VALUE),核心线程数满了之后,新提交的任务会往队列中堆积,如果不定义长度时,大批量任务来,一直堆积队列,容易造成oom,顾一般创建线程池使用该队列时最好给一个长度,如1024.
PriorityBlockingQueue:存储结构类似于LinkedBlockingQueue,是一个支持优先级的无界阻塞队列,默认情况下任务进入队列按照自然顺序升序排列。此时,队列中的任务还是会按照FIFO进行执行,但是如果自定义compareTo()来指定任务的顺序规则,或者在priorityBlockingQueue初始化时指定构造参数compareTo对任务进行排序,此时队列中任务就不是自然的顺序排列,队列任务执行,优先级高的会优先执行(使用的是堆算法),如果队列中任务堆积时间过长,容易造成某些优先级低的任务一直得不到执行(此队列和线程优先级相似),此队列目前应用较少,后续会继续关注,进行补充。
synchronousQueue: 无容量队列,存放和取用交替执行
线程池的拒绝策略
AbortPolicy:当前任务提交(添加到线程池被拒时),丢弃当前进入的任务,并抛出异常
DiscardPolicy:当前任务提交(添加到线程池被拒时),丢弃当前进入的任务,但不会抛出异常
DiscardOldestPolicy:当前任务提交(添加到线程池被拒时),会丢弃队列中最前端未处理的任务,将当前任务放到队列等待执行,如果任务一直不断进入,会重复上述操作。
CallerRunsPolicy:当前任务提交(添加线程池被拒时),当前任务会调用主线程执行,此时主线程阻塞,在主线程阻塞之前,如果有其它任务执行完成,显得任务进来就可以进入队列或者开启线程处理。
线程的提交方式(有无需要结果返回)
submit:可以提供Future类型的结果返回。因为其即可以接收runnable,也可以接收callable的请求,runnable时,future.get()获取到为null。submit方法不会抛出异常,除非你调用Fututre.get().
sumbit方法不会抛出异常。除非你调用Future.get()。
excute:只接收runnable的数据,从未无执行的结果返回,此方法可以抛出异常