线程池ThreadPoolExecutor

本文详细介绍了Java线程池的四种类型:CachedThreadPool、FixedThreadPool、ScheduledThreadPool和SingleThreadExecutor,以及它们各自适用的场景。线程池的配置包括核心池大小、最大线程数、空闲线程存活时间等参数。文章还讨论了无界队列和有界队列的选择,以及SynchronousQueue的特性。线程池的拒绝策略包括CallerRunsPolicy、AbortPolicy、DiscardPolicy和DiscardOldestPolicy。最后,根据CPU密集型、IO密集型和混合型任务,给出了线程池大小的建议配置。

1线程池有哪几种?

对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置(就是方法名一样,参数个数不一样)

CachedThreadPool()可缓存线程池:适用:执行很多短期异步的小程序或者负载较轻的服务器,因为这个线程池会复用上次的线程,无需创建,如果线程任务需要1毫秒,但是创建线程需要10毫秒,不划算,所以短期用这个,复用线程,节约创建时间。。。。实际很少用,因为线程任务很少小于创建线程时间的场景

FixedThreadPool()定长线程池 适用:执行长期的任务,性能好很多

ScheduledThreadPool()定长线程池 适用:周期性执行任务的场景:定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

//场景:如程序启动后,要等3秒才能加载某些东西,因为要初始化
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.schedule(()->{
    System.out.println("===我要延迟3秒=");
},3, TimeUnit.SECONDS);

SingleThreadExecutor()单线程化的线程池 适用:一个任务一个任务执行的场景

demo代码参考

BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(3);

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 1, TimeUnit.HOURS, queue,

new ThreadPoolExecutor.CallerRunsPolicy());

2.1 无界队列

队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。最近工作中就遇到因为采用LinkedBlockingQueue作为阻塞队列,部分任务耗时80s+且不停有新任务进来,导致cpu和内存飙升服务器挂掉。

2.2 有界队列

ArrayBlockingQueue

2.3 同步移交

如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

 

开始是没有常见线程的,第一个任务进来后会创建线程,然后第二个进来创建一个新的线程(而不是复用第一个线程),如果核心线程是5,就会创建5个线程,然后直接把任务分给这些线程,当核心线程5个创建完后,还有任务来就直接放队列里面,然后核心线程以后任务从队列取,然后队列满后,又增加线程到最大线程

corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止(释放临时创建的线程)。默认情况下,

只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用(也就是说这个参数是针对临时创建多出来的那3个线程的,如核心5,最大8,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数

为0;

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

 

 

CallerRunsPolicy

java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy

当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。

第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。

第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。

 AbortPolicy

java.util.concurrent.ThreadPoolExecutor.AbortPolicy

拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略

DiscardPolicy

java.util.concurrent.ThreadPoolExecutor.DiscardPolicy

当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

DiscardOldestPolicy

java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy

如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险

如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

重点理解:

 假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。

  因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;

  当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;

  如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;

  然后就将任务也分配给这4个临时工人做;

  如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。

  当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。

  这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。

也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。

3.1)CPU密集型( 此时cpu会比较忙)

尽量使用较小的线程池,一般Cpu核心数+1

3.2)IO密集型( 此时cpu会比较空闲)

方法一:可以使用较大的线程池,一般CPU核心数 * 2

方法二:(线程等待时间与线程CPU时间之比 + 1)* CPU数目

3.3)混合型

可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿甘带你学java

感谢支持

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

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

打赏作者

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

抵扣说明:

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

余额充值