创建自己的线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数含义
- corePoolSize :线程池基本大小
- maximumPoolSize :线程池中能拥有最多线程数的最大值
- keepAliveTime :空闲线程存活时间
- unit :线程存活保持时间单位TimeUnit.MILLISECONDS
- workQueue:用于缓存任务的阻塞队列
- threadFactory: threadFactory 线程工厂
- handler:拒绝策略
博主的实例
/**
* 队列大小
*/
private static final int QUEUE_SIZE = 1000;
/**
* 线程大小
*/
private static final int POOL_SIZE = Runtime.getRuntime().availableProcessors();
/**
* 初始化队列
*/
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(QUEUE_SIZE);
/**
* 初始化线程池 - 拒绝策略为跑出异常,不阻断流程
* 线程池设置为系统核心数的2倍,
*/
private Executor pool = new ThreadPoolExecutor(POOL_SIZE*2, POOL_SIZE*4, 0L, TimeUnit.MILLISECONDS
, queue, new ThreadFactoryBuilder().setNameFormat("getLafzRisk-%d").build(),new ThreadPoolExecutor.CallerRunsPolicy());
线程池大小
多线程执行的任务类型
1. CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
当线程数量太小,同一时间大量请求将被阻塞在线程队列中排队等待执行线程,此时 CPU 没有得到充分利用;当线程数量太大,被创建的执行线程同时在争取 CPU 资源,又会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率
Runtime.getRuntime().availableProcessors() + 1
2. IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
I/O 密集型任务:这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
Runtime.getRuntime().availableProcessors() * 2
3. 混合型任务
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。
一般多线程执行的任务类型可以分为 CPU 密集型和 I/O 密集型,根据不同的任务类型,我们计算线程数的方法也不一样。
在平常的应用场景中,我们常常遇不到这两种极端情况,那么碰上一些常规的业务操作,比如,通过一个线程池实现向用户定时推送消息的业务,我们又该如何设置线程池的数量呢?
此时我们可以参考以下公式来计算线程数:
线程数 =N(CPU 核数)*(1+WT(线程等待时间)/ST(线程时间运行时间))
IO密集时,大部分线程都被阻塞,故需要多配置线程数:
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右
例如:8核CPU:8/ (1 - 0.9) = 80个线程数
我们可以通过 JDK 自带的工具 VisualVM 来查看 WT/ST 比例
阻塞队列
LinkedBlockingQueue底层是单向链表,只有一个后继指针
ArrayBlockingQueue底层是数组
相同点:
ArrayBlockingQueue和LinkedBlockingQueue都是通过condition通知机制来实现可阻塞式插入和删除元素,并满足线程安全的特性;
不同点:
ArrayBlockingQueue底层是采用的数组进行实现,
LinkedBlockingQueue则是采用链表数据结构实现;
ArrayBlockingQueue插入和删除数据,只采用了一个lock,而LinkedBlockingQueue则是在插入和删除分别采用了putLock和takeLock,这样可以降低线程由于线程无法获取到lock而进入WAITING状态的可能性,从而提高了线程并发执行的效率
线程工厂
guava 包中的 com.google.common.util.concurrent
new ThreadFactoryBuilder().setNameFormat(“getLafzRisk-%d”).build() 设置 threadFactory
或者继承ThreadFactory 类自定义线程工厂
拒绝策略
AbortPolicy:默认的拒绝策略就是AbortPolicy。直接抛出异常。
CallerRunsPolicy:在任务被拒绝添加后,由向线程池提交任务的线程来执行该任务
DiscardPolicy:采用这个拒绝策略,会被线程池直接抛弃,不会抛异常也不会执行。
DiscardOldestPolicy:当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。
系统提供的工具(不推荐)
Executors类的底层实现便是ThreadPoolExecutor! Executors 工厂方法有:
Executors.newCachedThreadPool():无界线程池,可以进行自动线程回收
Executors.newFixedThreadPool(int):固定大小线程池
Executors.newSingleThreadExecutor():单个后台线程