Java之线程池ThreadPoolExecutor技术

2020-03-26  本文已影响0人  麦穗一足

一 、 基础知识

  1. 为什么要使用线程池呢?
    • 在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题。因此,在大多数并发框架中都会使用线程池技术来管理线程,那么使用线程池管理线程主要有下面三点好处:
      1. 降低资源消耗。 通过复用已经存在的线程和降低线程关闭的次数来尽可能降低系统的消耗;
      2. 提升系统响应速度。通过复用线程,省去创建线程的过程,因此整体上提升了系统的响应速度。
      3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统的资源,还会降低系统的稳定性,因此,需要使用线程池管理线程。
  2. Java 线程的整体架构体系
    1. Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类 。


      image.png
    2. 三个JDK 线程实现类
      • Executors.newFixedThreadPool(int):执行长期任务性能好,创建一个线程池,一个池中有N个固定线程,有固定线程数的线程池。
        // 代码实现  模拟十个人去银行办理业务,然后有5个窗口进行业务
        ExecutorService executorService = Executors.newFixedThreadPool(5); //固定线程数
        for (int i = 0; i < 10; i++) {
//            Thread.sleep(20);
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+" 办理业务");
            });
        }
        executorService.shutdown();
image.png
        // 代码实现 
        ExecutorService executorService = Executors.newSingleThreadExecutor(); //一池子一个工作线程类似银行只有一个窗口
        for (int i = 0; i < 10; i++) {
//            Thread.sleep(20);
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+" 办理业务");
            });
        }
        executorService.shutdown();
image.png
      // 代码实现
       ExecutorService executorService = Executors.newCachedThreadPool(); //可扩容线程池
        for (int i = 0; i < 10; i++) {
//            Thread.sleep(20);
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+" 办理业务");
            });
        }
        executorService.shutdown();
image.png
  1. ThreadPoolExecutor
  1. 线程池用哪个?生产中如设置合理参数呢 ?
    1. 线程池的拒绝策略 。
      • 什么是线程池的拒绝策略呢:等待队列已经排满了,再也塞不下新任务了同时,线程池中的max线程也达到了,无法继续为新任务服务。这个是时候我们就需要拒绝策略机制合理的处理这个问题。
      • JDK内置的4个拒绝策略(以下内置拒绝策略均实现了
        RejectedExecutionHandle接口)
        1. AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
        2. CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不
          会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
        3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中尝试再次提交当前任务。
        4. DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。
    2. 工作中我们选择什么线程池呢? 这个是面试常问的考点,如果你要是回答JDK默认的三种线程池技术你就完蛋了,代表你只是了解一些,根本没有用过。
      • 答案是一个都不用,工作中只能用自定义的。
      • JDK 都提供了为什么不用呢?
        • 这就跟源码有关系了,我们看看源码。
      //Executors.newFixedThreadPool() 源码
      public static ExecutorService newFixedThreadPool(int nThreads) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
      }
      
      // Executors.newSingleThreadExecutor()
      public static ExecutorService newSingleThreadExecutor() {
      return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>()));
      }
      
      //Executors.newCachedThreadPool()
      public static ExecutorService newCachedThreadPool() {
      return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
      }
      
       - 看看JDK给我们自带的实现,之前我们说过7大参数,那么现在看看5个参数 其中这些参数设计的及其不合理(newCachedThreadPool 中的maximumPoolSize设置的是Integer.MAX_VALUE,有些太大了,这样会导致系统失败的),根本不符合企业的要求,所以我们要自定义。
      
      • 自定义线程池(代码要会手写)
      ThreadPoolExecutor threadPoolExecutor = new         ThreadPoolExecutor(
              2,
              5,
              3L,
              TimeUnit.SECONDS,
              new LinkedBlockingQueue<>(3),
              Executors.defaultThreadFactory(),
              new ThreadPoolExecutor.DiscardPolicy());
      
      for (int i = 0; i < 9; i++) {
      //Thread.sleep(20);
          threadPoolExecutor.execute(()->{
              System.out.println(Thread.currentThread().getName()+" 办理业务");
          });
      }
      threadPoolExecutor.shutdown();
      
      • 但是这些参数我们要怎么设置呢? 其中最重要的就是配置最大线程数
        1. 如果线程池要处理的任务是cpu密集型,那么最大的任务就是cpu核数+ 1(但是我们不能写死了 要使用代码自动获取Runtime.getRuntime().availableProcessors())。
        2. 如果线程池要处理的任务是IO密集型,那么最大的任务就是cpu核数/阻塞系数。
上一篇 下一篇

猜你喜欢

热点阅读