Java【线程池】你应该了解的所有知识点

为什么使用线程池

1.降低资源消耗 通过重复利用已经创建的线程来避免频繁的创建销毁线程带来的开销

2.提高响应速度 已经创建好线程 可以省去创建时间来直接处理任务

3.增加线程的可管理性,可以通过线程池来对线程进行资源分配调优和监控

线程池的核心类和属性

  • corePoolSize:即线程池的核心线程数量,其实也是最小线程数量。不设置allowCoreThreadTimeOut 的情况下,核心线程数量范围内的线程一直存活。线程不会自行销毁,而是以挂起的状态返回到线程池,直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。
  • maximumPoolSize:即线程池的最大线程数量
  • keepAliveTime和unit:超出核心线程数后的存活时间和存活单位
  • workQueue:是一个阻塞的 queue,用来保存线程池要执行的所有任务。通常可以取下面四种类型:

1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
4)PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.

  • ThreadFactory:我们一般用Executors.defaultThreadFactory()默认工厂,为什么要用工厂呢,其实就是规范了生成的Thread。避免调用new Thread创建,导致创建出来的Thread可能存在差异
  • handler:当队列和最大线程池都满了之后的拒绝策略。

1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录
日志或持久化存储不能处理的任务

创建完线程池后使用也很简单,带返回值和不带返回值,传入对应传入Runnable或者Callable接口的实现

//无返回值
executor5.execute(() -> System.out.println("jack xushuaige"));
//带返回值
String message = executor5.submit(() -> { return "jack xushuaige"; }).get();

线程池为什么使用阻塞队列 不阻塞的不可以么?

阻塞队列是线程安全的。使用非阻塞队列需要额外的管理

线程池使用阻塞队列的原因主要是为了解决线程之间的协作和任务管理问题。阻塞队列可以让线程池更容易地处理以下两个方面:

  1. 线程安全:阻塞队列确保在同一时间只有一个线程可以访问队列,避免了多个线程同时操作队列时可能出现的问题。
  2. 控制任务数量:阻塞队列可以帮助线程池管理任务数量。当所有线程都在忙碌时,新任务会在队列中等待。如果队列已满,提交任务的线程会暂停,直到队列有空位。这有助于防止任务数量过多,导致系统资源不足。
    使用非阻塞队列可能会导致任务过多,消耗系统资源,以及需要额外的同步措施来保证线程安全。因此,线程池通常选择使用阻塞队列,因为它可以简化线程安全问题,并有效地管理任务数量。

## 线程池的运作流程描述

  1. 任务提交:用户通过调用 ExecutorServicesubmit()execute() 方法提交任务(实现了 RunnableCallable 接口的对象)到线程池。
  2. 线程创建:当线程池中的线程数小于 corePoolSize(核心线程数)时,线程池会为新提交的任务创建一个新的线程并执行任务。如果线程数已达到 corePoolSize,新提交的任务将被添加到任务队列(BlockingQueue)中等待执行。
  3. 任务队列:任务队列用于存储等待执行的任务。当线程池中的线程数达到 corePoolSize,新提交的任务会被添加到任务队列。线程池中的空闲线程会从任务队列中取出任务并执行。
  4. 线程扩容:如果任务队列已满且线程池中的线程数小于 maximumPoolSize(最大线程数),线程池会创建新的线程来执行新提交的任务。如果线程数已达到 maximumPoolSize,线程池将根据拒绝策略(RejectedExecutionHandler)处理新提交的任务。
  5. 线程回收:线程池中的线程在完成任务后会变为空闲状态,等待执行新任务。如果空闲线程的空闲时间超过 keepAliveTime(保持活跃时间),且线程池中的线程数大于 corePoolSize,则这些空闲线程将被回收。
  6. 线程池关闭:用户可以通过调用 ExecutorServiceshutdown()shutdownNow() 方法来关闭线程池。关闭线程池后,线程池将不再接受新任务,但会继续执行已提交的任务。如果使用 shutdownNow() 方法,线程池会尝试取消尚未完成的任务并立即关闭。

有了这些知识,那产生了一些问题
问题:那我是不是说可以这么理解 我创建线程池 核心线程数5 最大线程数10 阻塞队列容量100,当我顺序提交99个任务,其中5个在核心线程执行着 ,剩下的94个在阻塞队列里,此时线程池是不是创建第六个线程来执行任务的?

回答 是的

问题:如果线程池的设计是这样的,我设置阻塞队列容量为100,这个时候我顺序提交200个任务,那是不是就直接触发拒绝策略了

回答 是的

问题:那同样存在另外一个问题 假如我的阻塞队列使用的是优先级队列,且最大容量为100 ,核心线程数5 最大线程数10,我按优先级从高到低 依次发送115个任务,核心线程数5个线程正在执行优先级最高的5个任务,队列里存放在优先级从6到第105个任务,优先级为106到115的任务创建了最大线程数来执行,这样是不是就不满足优先级高的任务先执行了?
回答 是的

当核心线程数正在执行优先级最高的5个任务,阻塞队列存放着优先级从6到105的任务,而优先级为106到115的任务将创建额外的线程(超过核心线程数)来执行时,优先级高的任务不一定先执行。这是因为线程池会在以下情况下创建额外的线程:
当阻塞队列已满,且线程池中的线程数小于最大线程数时,线程池会创建额外的线程来执行新提交的任务。
在例子中,当阻塞队列已满(容量达到100),线程池会创建额外的线程来执行优先级为106到115的任务。这将导致这些任务可能在优先级为6到105的任务之前执行,从而不满足优先级高的任务先执行的要求。

为了解决这个问题,您可以考虑以下方法:

增大阻塞队列的容量,以便能够容纳更多的任务。这样,优先级为106到115的任务将被添加到阻塞队列中,而不会创建额外的线程来执行。
调整线程池的配置,例如减小核心线程数或最大线程数,以便更好地满足优先级高的任务先执行的需求。

如何合理配置线程池的大小

线程池大小不是靠猜,也不是说越多越好,最好的方式还是根据实际情况测试得出最佳配置。

  • CPU 密集型:主要是执行计算任务,响应时间很快,CPU 一直在运行,这种任务 CPU 利用率很高,会增加上下文切换,应当分配较少的线程,比如 CPU core+1
  • IO 密集型:主要是进行 IO 操作,执行 IO 操作的时间较长,由于线程并不是一直在运行,这时 CPU 利用率不高,可以增加线程池的大小,比如 CPU 2*core+1

直接开启所有核心线程的方法

默认情况下,即使是核心线程也只能在新任务到达时才创建和启动。但是我们可以使用 prestartCoreThread(启动一个核心线程)或 prestartAllCoreThreads(启动全部核心线程)方法来提前启动核心线程。

非核心线程怎么实现keepAliveTime后死亡

利用阻塞队列的方法,在获取任务时通过阻塞队列的 poll(time,unit) 方法实现的在延迟死亡

非核心线程 和核心线程有什么区别?

线程池内部是不区分核心线程和非核心线程的。只是根据当前线程池的工作线程数来进行调整,因此看起来像是有核心线程于非核心线程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值