多线程之线程池

·  阅读 356

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

创建线程会产生系统开销,并且每个线程会占用一定的内存等资源,同时线程的销毁也需要带来一定的压力。过多的线程还会带来由于上下文切换等等的性能损耗。

使用线程池的好处:
1)提高响应速度:通过复用线程可以消除线程创建销毁带来的延迟,提示响应速度
2)降低资源消耗线程池可以统筹内存和CPU的使用,避免资源使用不当,线程池会根据配置和任务数量灵活控制线程数量,不够就创建,多了就回收,避免线程过多导致内存溢出,过少导致资源浪费
3)提高线程可管理行线程池可以统一管理资源,统一进行分配、调优、监控。

一、线程池参数

1.1 线程池核心参数

1)corePoolSize 核心线程数。默认情况核心线程会一直存活。

2)maxPoolSize 最大线程数。决定线程池最多可以创建多少个线程

3)KeepAliveTime+时间单位 设置线程空闲时间,当线程空闲时间超过KeepAliveTime就会被销毁。allowCoreThreadTimeOut 参数可以决定核心线程若空闲时间过长,是否可以被回收

4)ThreadFactory 线程工厂。用来创建新线程,可以对线程的属性进行定制,比如线程group、线程名称、优先级等。

5)workQueue 存放任务的队列,即缓冲队列。

6)Handler 任务被拒绝时的策略 拒绝时机:

  • 调用shutdown等方法关闭线程池后,此时如果再向线程池提交任务,则遭到拒绝
  • 线程池没有能力处理任务时,即线程数达到了maxPoolSize,切workQueue已满

1.2 线程池拒绝策略

拒绝策略统一实现java.util.concurrent.RejectedExecutionHandler java在ThreadPoolExecutor中提供了其4中实现:

  • AbortPolicy 当需要拒接任务时,直接抛出RejectedExecutionException运行时异常
  • DiscardPolicy 当需要拒绝任务时直接丢弃,也不会给任何通知,可能会造成数据丢失
  • DiscardOldestPolicy 当需要拒绝任务时,丢弃任务队列中最长时间未被执行的任务,同样存在数据丢失风险
  • CallerRunsPolicy 当需要拒绝任务时,则将此任务交给提交该任务的线程去执行。不会造成数据丢失,而且当线程池满负载时,新来的任务由提交任务的线程去执行,这样主线程因为执行任务而被占用,也就减缓了提交新任务的速度,而线程池也可再次期间执掉一部分任务,腾出空间,相当于给了线程池一个缓冲期,并且相当于有一个负向反馈。

1.3 线程池常用阻塞队列

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • SynchronizedQueue

内部没有缓冲区

  • DelayedWorkQueue

三、线程池运行过程

image.png

四、 常见线程池

4.1 FixedThreadPool

固定线程数,无界队列,由于无界队列,会OOM。适用于任务数量不均匀、对内存压力不敏感场景。

4.2 CachedThreadPool

由于maximumPoolSize为Integer,MAX_VALUE,所以最终创建的线程数量会达到操作系统的上限或者导致内存不足。适用于要求低延迟的短期任务场景

4.3 ScheduledThreadPool

适用于定时任务执行场景,支持固定频率和固定延时

4.4 SingleThreadExecutor

单线程线程池,适用于需要一部执行但需要保证任务顺序的场景

  • SingleThreadScheduledExecutor

  • ForkJoinPool

采用分治思想,将大任务分解成多个小任务处理,然后合并结果

五、线程池异常捕获机制

总而言之,使用execute方法提交任务时,异常可以通过线程的UncaughtExceptionHandler机制捕捉异常。 而通过submit方式则不会,因为submit方法会将传入的runnable封装为future,而future内部执行时,直接进行了try catch,因而异常不会被抛出去,因为线程池也就感知不到异常。

www.cnblogs.com/ncy1/articl…

7.4 线程数量应该设置为多少

CPU密集型任务设置为CPU核心数的1~2倍 IO密集型任务公式:CPU核心数*(1+平均等待时间(I/O)/平均工作时间(cpu计算))

分类:
后端
标签:
分类:
后端
标签: