线程池的创建
Executors:工厂类 用于创建并返回不同类型的线程池,但是一般使用ThreadPoolExecutor 去创建。
使用线程池的好处
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
五种线程池类型
newScheduledThreadPool:无限大小的线程池
创建一个定长线程池,支持定时及周期性任务执行。
newFixedThreadPool:可重用固定线程数的线程池
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool:创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
newSingleThreadExecutor:只有一个线程的线程池
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去.
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多
线程池大小分配
线程池究竟设置多大要看你的线程池执行的什么任务了,CPU密集型、IO密集型、混合型,任务类型不同,设置的方式也不一样。 任务一般分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。
1、CPU密集型 尽量使用较小的线程池,一般Cpu核心数+1
2、IO密集型
方法一:可以使用较大的线程池,一般CPU核心数 *2
方法二:(线程等待时间与线程CPU时间之比 + 1)* CPU数目
3、混合型 可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定
实现runnable和callable接口的区别
runnable不会返回结果或者抛出检查异常,但是callable可以。
工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
执行execute和 submit方法的区别
execute用于提交不需要返回值的任务 无法判断是否成功
submit 用于提交需要返回值的任务。会返回一个Futrue类型的对象,用来判断任务是否成功。
线程池的一个基本流程
线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:
如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊缓冲队列。
如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程来处理被添加的任务。
如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终⽌。这样,线程池可以动态的调整池中的线程数
实际上执行它的流程 是 三部分 核心线程数 任务队列 和 最大线程数
有任务 创建线程执行,当线程数等于核心 线程数则将新来的任务放入队列等待,当任务队列满了时则会继续创建新线程执行任务 ,当到达最大线程数的时候 进入shutdown 不在接受新任务
线程执行任务两种情况 1是直接创建新线程直接执行这个任务 2是从工作队列中获取
线程池的五种状态
running:能够接收新任务,以及对已添加的任务进行处理。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
SHUTDOWN:不接受新任务,处理已有的任务
STOP:调用线程池的shutdownNow()接口时,不接受新任务,不处理已经添加的任务,中断正在处理的任务
tidying:所有任务已终止,会执行钩子函数terminated(),需要重写
TERMINATED:彻底终止。 terminated
核心参数
corePoolSize 代表核⼼线程数,也就是正常情况下创建⼯作的线程数,这些线程创建后并不会消除,⽽是⼀种常驻线程。
maxinumPoolSize 代表的是最⼤线程数,它与核⼼线程数相对应,表示最⼤允许被创建的线程数,⽐如当前任务较多,将核⼼线程数都⽤完了,还⽆法满⾜需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最⼤线程数。
keepAliveTime 、 unit 表示超出核⼼线程数之外的线程的空闲存活时间,也就是核⼼线程不会消除,但是超出核⼼线程数的部分线程如果空闲⼀定的时间则会被消除,我们可以通过setKeepAliveTime 来设置空闲时间。
workQueue ⽤来存放待执⾏的任务,假设我们现在核⼼线程都已被使⽤,还有任务进来则全部放⼊队列,直到整个队列被放满但任务还再持续进⼊则会开始创建新的线程。
ThreadFactory 实际上是⼀个线程⼯⼚,⽤来⽣产线程执⾏任务。我们可以选择使⽤默认的创建⼯⼚,产⽣的线程都在同⼀个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择⾃定义线程⼯⼚,⼀般我们会根据业务来制定不同的线程⼯⼚。
Handler 任务拒绝策略,有两种情况,第⼀种是当我们调⽤ shutdown 等⽅法关闭线程池后,这时候即使线程池内部还有没执⾏完的任务正在执⾏,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另⼀种情况就是当达到最⼤线程数,线程池已经没有能⼒继续处理新提交的任务时,这也就是拒绝。
四种拒绝策略(饱和策略) 线程已满
主要有4种拒绝策略:
AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
CallerRunsPolicy:只用调用者所在的线程来处理任务
DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
DiscardPolicy:直接丢弃任务,也不抛出异常
阻塞队列(用在消费者生产者模型)
阻塞队列 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用 。
java线程池中队列的常用类型有哪些?
ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue 一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue 。
SynchronousQueue 一个不存储元素的阻塞队列。
PriorityBlockingQueue 一个具有优先级的无限阻塞队列。PriorityBlockingQueue 也是基于最小二叉堆实现
DelayQueue 只有当其指定的延迟时间到了,才能够从队列中获取到该元素。 DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。