线程池基本知识点

线程池化的好处

首先,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二,提高响应速度,任务到达时,无需等待线程的创建,线程池中的核心线程可以立马执行。
第三,通过对线程池的管理,可以保证对线程的有效操作。

线程池的工作流程

当任务来临时,首先判断核心线程数是否已满,未满则创建线程执行任务(corePoolSize)
若核心线程数已达最大值,判断队列是否已满,若未满则加入队列中(workQueue)
若队列已满,判断最大线程数是否已达最大值,若未满创建线程执行任务(maximumPoolSize)
若线程数到达,则采用拒绝策略处理;(handler参数)

线程池的创建

new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

参数解释:

corePoolSize - 线程池核心池的大小。
maximumPoolSize - 线程池的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 的时间单位。
workQueue - 用来储存等待执行任务的队列。
threadFactory - 线程工厂。
handler - 拒绝策略

对于上述的流程其实可以想象成线程池就好比餐馆,任务好比顾客;核心线程数比作员工数;队列好比餐馆外的凳子;
当顾客来时,首先看餐厅员工是否足够去招待,不够则让顾客在外面稍作等待;如果凳子也坐满了;就找些临时工(即创建普通线程)去负责;如果临时工的数量也到达上限了 那就只有舍弃顾客了。当任务完成后,如果有临时工 则辞退临时工。

不允许使用Executors去创建

阿里巴巴开发手册:“线程池的创建不允许Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险”

通过Executors创建线程池的三种方式
newCachedThreadPool => 创建可缓存的线程池
newSingleThreadExecutor => 创建单线程的线程池
newFixedThreadPool => 创建固定长度的线程池
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

核心线程数(corePoolSize ):0,
最大线程数(maximumPoolSize ):Integer的最大值
持续时间(keepAliveTime ):60L
单位(unit):秒
工作队列:SynchronousQueue

当一个任务提交时,核心线程数为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。
对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE(2^31 - 1)非常大,可以认为是可以无限创建线程的,但是在资源有限的情况下容易引起OOM异常

newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

核心线程数(corePoolSize ):1,
最大线程数(maximumPoolSize ):1
持续时间(keepAliveTime ):0L
单位(unit):秒
工作队列:LinkedBlockingQueue(长度为Integer.MAX_VALUE)

当任务来临时,首先创建一个核心线程来执行任务,如果超过核心线程数,会将任务放入到队列中去,但是由于队列所容纳的长度为Integer的最大值,因此可以往队列中插入无限多的任务,在资源有限的情况下会引起OOM异常。另外,由于队列长度原因,最大线程数和持续时间参数其实是无效的。

FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

corePoolSize => 1,核心线程池的数量为1
maximumPoolSize => 1,只可以创建一个非核心线程
keepAliveTime => 0L
unit => 秒
workQueue => LinkedBlockingQueue

它和SingleThreadExecutor类似,唯一的区别就是核心线程数不同,并且由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常

拒绝策略

当队列满了,线程池中的线程数也已达到了最大线程数,则会执行拒绝策略
AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
DiscardPolicy:不能执行的任务将被删除
DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)

创建线程工厂(ThreadFactory)

方式1:DaemonThreadFactory 是创建守护线程的工厂
方式2:DefaultThreadFactory 默认方式 此工厂创建的线程 不是守护线程 优先级为5 堆大小为0 有线程组 有线程名字

任务提交(excute和submit)

excute()与submit()都可以向线程池提交任务;不同的是,excute()提交没有返回值的任务,无法知道线程池的执行结果。
submit()方法用于提交需要返回值的任务,线程池会返回一个future类型的对象来判断任务是否执行完成。future.get()方法来获取返回值,但是get()方法会阻塞当前一段时间后返回。
在这里插入图片描述
在这里插入图片描述

工作中线程池的创建(使用ThreadPoolExecutor

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 3,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100),
                Executors.defaultThreadFactory(),
                //拒绝策略丢弃任务并抛出异常
                new ThreadPoolExecutor.AbortPolicy());

线程池的参数配置与CPU核数有关:
CPU密集型
配置尽可能小的线程
CPU核数+1个
场景:
任务需要大量运算,没有阻塞,CPU一致运行
IO密集型
配置尽可能多的线程
CPU核数 * 2。
场景:
线程不是一直执行任务,可以多分配一些线程数
通过Runtime.getRuntime().availableProcessors();可以获取到CPU核数

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值