高并发如何使用线程池

创建线程池

我们可以通过自定义ThreadPoolExecutor或者jdk内置的Executors来创建一系列的线程池

  • newFixedThreadPool: 创建固定线程数量的线程池
  • newSingleThreadExecutor: 创建单一线程的池
  • newCachedThreadPool: 创建线程数量自动扩容, 自动销毁的线程池
  • newScheduledThreadPool: 创建支持计划任务的线程池

潜在宕机风险

使用Executors来创建要注意潜在宕机风险

1FixedThreadPool和SingleThreadPoolPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能因为无限制任务队列而耗尽资源,只是出现问题的概率较小。如果新请求的到达速率超过了线程池的处理速率,那么新到来的请求将被累积起来可能会堆积大量的请求,从而导致 OOM(内存溢出).

解决方法:使用有界队列可以防止资源耗尽,但也因此必须要考虑饱和策略。因为默认的中止策略可能不是我们想要的

2 CachedThreadPool和ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.

解决方法:这种情况可以采用固定大小的线程池来解决这个问题。

 

可能有大量请求的线程池场景中, 更推荐自定义ThreadPoolExecutor来创建线程池

线程池大小配置

一般根据任务类型进行区分, 假设CPU为N核

  • CPU密集型任务需要减少线程数量, 降低线程切换开销,可配置线程池大小为N + 1.
  • IO密集型任务则可以加大线程数量, 可配置线程池大小为 N * 2.
  • 混合型任务则可以拆分为CPU密集型与IO密集型, 独立配置.

自定义阻塞队列BlockingQueue

主要存放等待执行的线程, ThreadPoolExecutor中支持自定义该队列来实现不同的排队队列.

  • ArrayBlockingQueue:先进先出队列,创建时指定大小, 有界;
  • LinkedBlockingQueue:使用链表实现的先进先出队列,默认大小为Integer.MAX_VALUE;
  • SynchronousQueue:不保存提交的任务, 数据也不会缓存到队列中, 用于生产者和消费者互等对方, 一起离开.
  • PriorityBlockingQueue: 支持优先级的队列

饱和策略

ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改

  • 中止(Abort)策略是默认的饱和策略,该策略将抛出未检查的RejectedExecutionException。
  • 抛弃(Discard)策略会悄悄抛弃该任务。
  • 抛弃最旧的(Discard-Oldest)策略则会抛弃下一个将被执行的任务,然后尝试重新提交新的任务。(如果工作队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将“抛弃最旧的”饱和策略和优先级队列放在一起使用)
  • 调用者运行(Caller-Runs)策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者。当线程池中所有线程都被占用,并且工作队列被填满后,下一个任务会在调用executor时在主线程中执行。

 

你可能感兴趣的:(java基础)