线程池

什么是线程池?其工作原理是什么,好处是什么?

一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行 任务的任务队列(阻塞队列)。默认情况下,在创建了线程池后,线程池中的线程数为 0。
任务提交给 线程池之后的处理策略如下:

  1. 如果此时线程池中的数量小于 corePoolSize(核心池的大小),即 使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务(也 就是每来一个任务,就要创建一个线程来执行任务)。
  2. . 如果此时线程池中的数量大于等于 corePoolSize,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列,则该任务会等待空闲线程将其 取出去执行。
  3. 如 果 此 时 线 程 池 中 的 数 量 大 于 等 于 corePoolSize , 缓 冲 队 列 workQueue 满,并且线程池中的数量小于 maximumPoolSize(线程池 最大线程数),建新的线程来处理被添加的任务。
  4. 如果 此时 线程 池中 的数量 大 于 等 于 corePoolSize, 缓 冲 队列 workQueue 满,并且线程池中的数量等于 maximumPoolSize,那么通 过 RejectedExecutionHandler 所指定的策略(任务拒绝策略)来处理此任务。

线程池的主要处理流程;
在这里插入图片描述

也就是处理任务的优先级为:核心线程 corePoolSize、任务队列 workQueue、最大线程 maximumPoolSize,如果三者都满了,使用 handler 处理被拒绝的任务。

在 corePoolSize 和 maximumPoolSize 之间的线程 数会被自动释放。当线程池中线程数量大于 corePoolSize 时,如果某线程 空闲时间超过 keepAliveTime,线程将被终止,直至线程池中的线程数目不 大于 corePoolSize。这样,线程池可以动态的调整池中的线程数。

使用线程池的好处:
1.通过重复利用已创建的线程,减少在创建和销毁线程上所花的时间以及系 统资源的开销。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就可以立即 执行。
3.提高线程的可管理性。使用线程池可以对线程进行统一的分配和监控。
4.如果不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内 存。

下面解释一下构造器中各个参数的含义:

corePoolSize:核心池的大小。默认情况下,在创建了线程池后,线程池中 的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中 的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多 少个线程。
keepAliveTime:默认情况下,只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize, 即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。
unit:参数 keepAliveTime 的时间单位。
workQueue:一个阻塞队列,任务缓存队列,即 workQueue,它用来存 放等待执行的任务。workQueue 的类型为 BlockingQueue,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小; 2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列 大小,则默认为 Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接 新建一个线程来执行新来的任务。在某次添加元素后必须等待其他线程取走后才能继续添 加。
ThreadFactory:线程工厂,主要用来创建线程。
handler:表示当拒绝处理任务时的策略,有以下四种取值:
1.AbortPolicy:直接抛出异常(默认的)
2.DiscardPolicy:直接丢弃任务
3.DiscardOldestPolicy:丢弃队列中最旧(队头)的任务,并执行当前任务
4.CallerRunsPolicy:不用线程池中的线程执行,用调用者所在线程执行。

execute 和 submit 区别:
(1)、submit 有返回值,execute 没有返回值。 所以说可以根据任务有无返回 值选择对应的方法。
(2)、submit 方便异常的处理。 如果任务可能会抛出异常,而且希望外面的调 用者能够感知这些异常,那么就需要调用 submit 方法,通过捕获 Future.get 抛出的异常。

shutdown()和 shutdownNow()的区别:
shutdown()和 shutdownNow()是用来关闭线程池的。
shutdown 方法:此方法执行后不得向线程池再提交任务,如果有空闲线 程则销毁空闲线程,等待所有正在执行的任务及位于阻塞队列中的任务执行结 束,然后销毁所有线程。
shutdownNow 方法:此方法执行后不得向线程池再提交任务,如果有空 闲线程则销毁空闲线程,取消所有位于阻塞队列中的任务,并将其放入 List容器,作为返回值。取消正在执行的线程(实际上仅仅是设 置正在执行线程的中断标志位,调用线程的 interrupt 方法来中断线程)。

线程池的注意事项:
(1)线程池的大小:线程数目与 CPU 数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可 采用自适应算法来动态调整线程池的大小,以提高 CPU 的有效利用率和系统的整体性能。
(2)并发错误:多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避 免死锁现象的发生。
(3)线程泄露:这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中 就会发生线程泄漏现象。

简单线程池的设计

一个典型的线程池,应该包括如下几个部分:
1、线程池管理器(ThreadPool),用于启动、停用,管理线程池
2、工作线程(WorkThread),线程池中的线程
3、请求接口(WorkRequest),创建请求对象,以供工作线程调度任务的执行
4、请求队列(RequestQueue),用于存放和提取请求
5、结果队列(ResultQueue),用于存储请求执行后返回的结果

线程池管理器通过添加请求的方法向请求队列添加请求,这些请求事先需要实现请求接口,即传递工作函数、参数、结果处理函数、以 及异常处理函数。之后初始化一定数量的工作线程,这些线程通过轮询的方式不断查看请 求队列(RequestQueue),只要有请求存在,则会提取出请求,进行执行。然后,线程管理器调用方法查看结果队列是否有值,如果有值,则取出,调用结果处理函数执行。通过上述讲述,该系统的核心资源是请求队列和结果队列,工作线程通过轮训获得任务,主线程通过查看结果队列,获得执行结果。对这个队列的设计,要实现线程同步,以及一定阻塞和超时机制的设计, 以防止因为不断轮询而导致的过多 cpu 开销。
在这里插入图片描述

如何合理配置java线程池??如 CPU 密集型的任务,基本线程池 应该配置多大?IO 密集型的任务,基本线程池应该配置多大?用有界 队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取 最好的吞吐量?

(1)配置线程池时,CPU 密集型任务可以少配置线程数,大概和机器的 cpu 核数相 当,可以使得每个线程都在执行任务。
(2)IO 密集型任务则由于需要等待 IO 操作,线程并不是一直在执行任务,则配置尽 可能多的线程,2*cpu 核数。
(3)有界队列和无界队列的配置需区分业务场景,一般情况下配置有界队列,在一些 可能会有爆发性增长的情况下使用无界队列。
(4)任务非常多时,使用非阻塞队列使用 CAS 操作替代锁可以获得好的吞吐量。 synchronousQueue 吞吐率最高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值