创建自己的线程池,线程池详解

文章详细介绍了如何创建自定义的线程池,包括参数解释如核心线程数、最大线程数、存活时间等。对于线程池大小的选择,文章区分了CPU密集型任务和IO密集型任务,并给出了相应的线程数计算建议。此外,还讨论了阻塞队列的选择,线程工厂的定制以及拒绝策略的四种方式。最后,提到了Executors类提供的线程池工厂方法及其特点。
摘要由CSDN通过智能技术生成

创建自己的线程池

 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():单个后台线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羱滒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值