《Java并发编程实战》课程笔记(十五)

Executor 与线程池:如何创建正确的线程池?

线程池是一种生产者-消费者模式

  • 线程是一个重量级的对象,应该避免频繁创建和销毁。那如何避免呢?应对方案就是线程池。
  • 目前业界线程池的设计,普遍采用的都是生产者-消费者模式。线程池的使用方是生产者,线程池本身是消费者。

如何使用 Java 中的线程池

  • Java 并发包里提供的线程池,最核心的是 ThreadPoolExecutor,它强调的是 Executor,而不是一般意义上的池化资源。
  • ThreadPoolExecutor 的构造函数非常复杂,这个最完备的构造函数有 7 个参数。
    • corePoolSize
      • 表示线程池保有的最小线程数。
    • maximumPoolSize
      • 表示线程池创建的最大线程数。
    • keepAliveTime & unit
      • 一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。
      • 如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
    • workQueue
      • 工作队列。
    • threadFactory
      • 通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
    • handler
      • 通过这个参数你可以自定义任务的拒绝策略。
      • 如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。
      • ThreadPoolExecutor 已经提供了以下 4 种策略:
        • CallerRunsPolicy
          • 提交任务的线程自己去执行该任务。
        • AbortPolicy
          • 默认的拒绝策略,会 throws RejectedExecutionException。
        • DiscardPolicy
          • 直接丢弃任务,没有任何异常抛出。
        • DiscardOldestPolicy
          • 丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

使用线程池要注意些什么

  • 考虑到 ThreadPoolExecutor 的构造函数实在是有些复杂,所以 Java 并发包里提供了一个线程池的静态工厂类 Executors,利用 Executors 你可以快速创建线程池。
  • 目前大厂的编码规范中基本上都不建议使用 Executors 了:
    • 不建议使用 Executors 的最重要的原因是:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。
    • 使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 它,所以开发人员很容易忽略,因此默认拒绝策略要慎重使用。
    • 如果线程池处理的任务非常重要,建议自定义自己的拒绝策略,并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。
  • 虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是捕获所有异常并按需处理。

你可能感兴趣的:(Java,基础,java,笔记,jvm)