• 03 线程池:业务代码最常用也最容易犯错误的组件


    1. 线程池的声明需要手动进行new ThreadPoolExecutor形式。生产中常因为使用Executors类下方法,如newFixedThreadPool和newCachedThreadPool导致OOM问题。

    • newFixedThreadPool中虽然线程数可以限制,但任务数量是无限的,因为使用的是LinkendBlockingQueue无限队列来存储任务,任务不能及时处理,过多堆积,最终OOM;

    • newCachedThreadPool线程数是无上界的,而工作队列是SynchronousQueue没有存储空间的阻塞队列,即有请求到来就创建一个线程处理任务(线程是需要存储空间的),最终OOM无法创建线程;

    1. 手动创建线程的原因:

    • 根据自己的场景、并发情况来评估线程池的几个核心参数,如核心线程数、最大线程数、线程回收策略、工作队列类型以及拒绝策略,确保线程池工作行为符合要求,一般都需要设置有界工作队列和可控线程数

    • 任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数暴增、线程死锁、线程占用大量CPU、线程执行出现异常等问题时,往往会抓取线程栈。此时有意义线程名称,有助于定位问题。

    • 要用一些监控手段来观察线程池状态。

    1. 线程池默认工作行为如下:

    • 不会初始化corePoolSize个线程,有任务来了才会创建工作线程,即使核心线程有空闲,在没有到核心线程数前,对新任务创建新线程;

    • 当核心线程满了之后不会立即扩容线程池,而是把任务堆积到工作队列中;

    • 当工作队列满了后扩容线程池,一直到线程个数达到maximumPoolSize为止;

    • 如果队列已满且达到了最大线程后还有任务进来,按照拒绝策略处理;

    • 当线程数大于核心线程数时,线程等待keepAliveTime后没有任务要处理的话,收缩线程到核心线程数;

    1. 通过如下手段改变线程池默认工作行为:

    • 声明线程池后立即调用prestartAllCoreThreads方法,来启动所有核心线程;

    • 传入true给allowCoreThreadTimeOut方法,来让线程池在空闲的时候同样回收核心线程。

    1. 了解了线程池工作行为,如何设计一个不等任务队列满,而优先创建线程的线程池呢,如下两步:

    • 由于线程池在队列满,即无法加入队列情况下才扩容线程池,那么就可以重写队列offer方法,造成队列已满的假象;offer方法是往队尾插入任务,

    • 队列已满后,同时达到最大线程数,那么会触发线程池拒绝策略。我们可以自定义拒绝策略,当队列"已满"(假象)后,再真正把任务加入到队列中,通过线程池获取到队列,再通过put加入任务。

    • 参考stackoverflowtomcat

    • 代码案例

       static BlockingQueue queue = new LinkedBlockingQueue(10) {
           @Override
           public boolean offer(Object o) {
               if (size() == 0) {
                   return super.offer(o);
              } else {
                   return false;
              }
          }
      };
       static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 5,
               60, TimeUnit.SECONDS, queue, new RejectedExecutionHandler() {
           @Override
           public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
               try {
                   executor.getQueue().put(r);
                   if (executor.isShutdown()) {
                       throw new RejectedExecutionException(
                               "Task " + r + " rejected from ");
                  }
              } catch (Exception ex) {
                   ex.printStackTrace();
                   Thread.currentThread().interrupt();
                   return;
              }
          }
      });
       public static void main(String[] args) throws Exception{
           IntStream.rangeClosed(1, 10).parallel().forEach(i -> {
               threadPoolExecutor.execute(() -> {
                   System.out.println(i);
              });
          });
      }
    1. 线程池是用来复用的,一个系统根据业务类别用到几个线程池还好,但是要注意使用的时候不要根据请求或其他方法调用来创建线程池,避免造成创建太多线程池。一般就是系统初始化过程,线程池已经创建好,直接使用。

    2. 注意线程池不要混用,根据程序功能不同,不同策略要配置不同参数的线程池:

    • 对于执行慢、数量不大的IO任务,或许要考虑更多的线程数,而不需要太大的队列;

    • 对于吞吐量较大的计算型任务,线程数量不宜过多,可以是CPU核数或核数*2(过多线程造成的线程切换开销,并不能提高吞吐量),但可能需要较长的队列来做缓冲。

    1. Linux系统中可以使用wrk压测工具,这款工具可以调用lua脚本,还可以统计吞吐量、延迟、每次请求数据大小等

    2. Java8中的parallel stream功能,如IntStream.rangeClosed,可以很方便的并行处理集合中元素,其背后是共享同一个ForkJoinPool,默认并行度是CPU核数-1。对于CPU绑定的任务使用这样的配置比较合适。 如果集合操作设计同步IO操作(数据库操作、外部服务调用等)的话,建议自定义一个ForkJoinPool或普通线程池。

        public static void main(String[] args) throws Exception{
           ForkJoinPool forkJoinPool=new ForkJoinPool(3);
           //注意parallel()方法是将操作分成多个任务,多个任务一起完成这个循环
           //如果不使用这个方法,execute中的参数就是一个任务里面做了10次循环
           forkJoinPool.execute(()-> IntStream.rangeClosed(1,10).parallel().forEach(i->{
               System.out.println(Thread.currentThread().getName());
               System.out.println(i);
          }));
           forkJoinPool.shutdown();
           //下面方法是等待在执行shutdown之后,所有任务执行完就终止,否则就等待指定时间后终止
           forkJoinPool.awaitTermination(10, TimeUnit.SECONDS);
      }
  • 相关阅读:
    数据结构(树链剖分):NOI2014 购票
    数据结构(树链剖分):COGS 2109. [NOIP2015] 运输计划
    数据结构(树链剖分,堆):HNOI 2016 network
    快速傅里叶变换(FFT):COGS 2216. 你猜是不是KMP
    生成树的计数(基尔霍夫矩阵):BZOJ 1002 [FJOI2007]轮状病毒
    数据结构(线段树):BZOJ 3126: [Usaco2013 Open]Photo
    数位DP:SPOJ KPSUM
    动态规划(状态压缩):BZOJ 2621 [Usaco2012 Mar]Cows in a Skyscraper
    数据结构(并查集):COGS 260. [NOI2002] 银河英雄传说
    生成树的计数(基尔霍夫矩阵):UVAoj 10766 Organising the Organisation SPOJ HIGH
  • 原文地址:https://www.cnblogs.com/hujiapeng/p/13535178.html
Copyright © 2020-2023  润新知