一,ThreadPoolExecutor介绍
ThreadPoolExecutor 是Java原生的线程池,我们只需要简单配置一下就可以使用了
二,线程池配置详解
ExecutorService exe = new ThreadPoolExecutor(
//核心线程池大小
CORE_POOL_SIZE,
//最大线程池大小
MAXIMUM_POOL_SIZE,
//线程最大空闲时间
KEEP_ALIVE_TIME,
//时间单位
TimeUnit.MILLISECONDS,
//线程等待队列
new LinkedBlockingQueue<>(1024),
//线程创建工厂 (可选,默认值: Executors.defaultThreadFactory())
r -> new Thread(r),
//拒绝策略 (可选,默认值: AbortPolicy())
new ThreadPoolExecutor.AbortPolicy());
1. corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;
如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
2. maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize
3. keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用
4.TimeUnit
keepAliveTime的时间单位
TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒
5.workQueue
workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能
常用的BlockingQueue
ArrayBlockingQueue | 数组结构组成有界阻塞队列。 先进先出原则,初始化必须传大小,take和put时候用的同一把锁 |
LinkedBlockingQueue | 链表结构组成的有界阻塞队列 先进先出原则,初始化可以不传大小,put,take锁分离 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列, 排序,自然顺序升序排列,更改顺序:类自己实现compareTo()方法,初始化PriorityBlockingQueue指定一个比较器Comparator |
DelayQueue | 使用了优先级队列的无界阻塞队列 支持延时获取,队列里的元素要实现Delay接口。DelayQueue非常有用,可以将DelayQueue运用在以下应用场景。 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。 还有订单到期,限时支付等等。 |
SynchronousQueue | 不存储元素的阻塞队列 每个put操作必须要等take操作 |
LinkedTransferQueue | 链表结构组成的无界阻塞队列 Transfer,tryTransfer,生产者put时,当前有消费者take,生产者直接把元素传给消费者 |
LinkedBlockingDeque | 链表结构组成的双向阻塞队列 可以在队列的两端插入和移除,xxxFirst头部操作,xxxLast尾部操作。工作窃取模式。 |
6.ThreadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名
Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”
7.RejectedExecutionHandler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
(1)AbortPolicy:直接抛出异常,默认策略;
(2)CallerRunsPolicy:用调用者所在的线程来执行任务;
(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
(4)DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
三,合理的配置线程池
a. 区分任务类别
计算密集型:计算机的cpu数或计算机的cpu数+1(应付页缺失)
IO密集型:计算机的cpu数*2
混合型:拆分成计算密集型,IO密集型
Runtime.getRuntime().availableProcessors();获取当前机器中的cpu核心个数
b.尽量有界队列,不要使用无界队列