为什么使用线程池
平时我们在使用多线程的时候,通常都是架构师配置好了线程池的 Bean,我们需要使用的时候,提交一个线程即可,不需要过多关注其内部原理。
在学习一门新的技术之前,我们还是先了解下为什么要使用它,使用它能够解决什么问题:
- 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率
例如:
记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3
如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!
正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销
- 线程并发数量过多,抢占系统资源从而导致阻塞
我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况
运用线程池能有效的控制线程最大并发数,避免以上的问题
- 对线程进行一些简单的管理
比如:延时执行、定时循环执行的策略等
运用线程池都能进行很好的实现
创建一个线程池
在 Java 中,新建一个线程池对象非常简单,Java 本身提供了工具类java.util.concurrent.Executors
,可以使用如下代码创建一个固定数量线程的线程池:
ExecutorService service = Executors.newFixedThreadPool(10);
注意:以上代码用来测试还可以,实际使用中最好能够显示地指定相关参数。
我们可以看下其内部源码实现:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
在阿里巴巴代码规范中,建议我们自己指定线程池的相关参数,为的是让开发人员能够自行理解线程池创建中的每个参数,根据实际情况,创建出合理的线程池。接下来,我们来剖析下java.util.concurrent.ThreadPoolExecutor
的构造方法参数。
ThreadPoolExecutor 浅析
java.util.concurrent.ThreadPoolExecutor
有多个构造方法,我们拿参数最多的构造方法来举例,以下是阿里巴巴代码规范中给出的创建线程池的范例:
ThreadPoolExecutor service = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(), new ThreadPoolExecutor.AbortPolicy());
首先最重要的几个参数,可能就是:corePoolSize
,maximumPoolSize
,workQueue
了,先看下这几个参数的解释:
- corePoolSize:核心线程池的大小,如果核心线程池有空闲位置,这是新的任务就会被核心线程池新建一个线程执行,执行完毕后不会销毁线程,线程会进入缓存队列等待再次被运行
- maximumPoolSize:线程池能创建最大的线程数量。如果核心线程池和缓存队列都已经满了,新的任务进来就会创建新的线程来执行。但是数量不能超过maximunPoolSize,否侧会采取拒绝接受任务策略
- keepAliveTime:非核心线程能够空闲的最长时间,超过时间,线程终止
- unit:时间单位,和keepAliveTime配合使用
- workQueue:缓存队列,用来存放等待被执行的任务
- handler:拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,四种策略为
-
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
Executor接口有一个子接口ExecutorService,ExecutorService的实现类为AbstracExecutorService,而ThreadPoolExcutor正是AbstrcExecutorService的子类。
ThreadPoolExecutor还有两个常用的方法shutdown,关闭线程池
线程池的优点
-
重用存在的线程,减少对象的创建、消亡的开销
- 性能佳可以有效的控制最大的并发线程数,提高系统资源的利用率,避免竞争,堵塞
- 提供定时执行、单线程并发数控制等
Executors 四种线程池
- newFiledThreadPool
- newCachedThreadPool
- newSingleThreadExecutor
- newScheduledThreadPool
参考:https://segmentfault.com/a/1190000018399962
参考:https://www.cnblogs.com/zzuli/p/9386463.html