多线程记录(一)

211 阅读4分钟

1.1 ThreadPoolExecutor详解

1.1.1 ThreadPoolExecutor类核心属性

  • 线程池的创建主要核心类是ThreadPoolExecutor,也就是说,它的一个实例就是一个线程池对象,我们可以new一个对象,来创建初始化一个线程池。

  • 而对于ThreadPoolExecutor对象的实例化,有一些核心属性是我们必须要了解和掌握的。

     public ThreadPoolExecutor(int corePoolSize,
                                   int maximumPoolSize,
                                   long keepAliveTime,
                                   TimeUnit unit,
                                   BlockingQueue<Runnable> workQueue,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler){
       ...
     }
    
属性作用类型
corePoolSize核心线程池大小int
maximumPoolSize最大线程池大小int
keepAliveTime非核心线程的闲置时间,超过这个时间,将被回收long
TimeUnit时间单位TimeUnit
workQueue指定阻塞队列BlockingQueue
handle当线程池和队列已满的情况下,对新任务申请线程的请求采取什么策略进行处理,处理策略举例:直接丢弃该请求/抛出异常等。RejectedExecutionHandle
threadFactory指定线程创建工厂类,也就是常见线程用的ThreadFactory
  • corePoolSize它决定了当前线程池的最小线程创建数量,是不会被清除的。其它属性也非常好理解,这里不作过多解释。

  • 主要关注一下workQueue和handle:

    • workQueue指定一个BlockingQueue类型的阻塞队列,它的作用无非就是在当前线程池已满,没有闲置线程可用的情况下,新的任务请求到来后,被临时存放的一个容器,等待空闲出来的线程从中拿出来调用。

    • 我们还可以将阻塞队列分为无界有界两种类型的队列。无界队列LinkedBlockingQueue有界队列ArrayBlockingQueue。(下面Executors的笔记中,会有对这两类队列的分析,实际在看阿里开发手册时,不建议使用Executors来创建线程池,与无界队列有关)

    • handle则指定一个RejectedExecutionHandle,当线程池和阻塞队列已满,新的线程请求时,指定ThreadPoolExecutor对当前这个请求的一个处理策略,例如,直接丢弃或抛出异常等。也就是说这是一个策略类,它的实现策略类包括以下几个:

      策略类处理策略
      AbortPolicy抛出异常,拒绝处理
      DiscardPolicy直接丢弃
      DiscardOldestPolicy丢弃存在最老的任务,并执行新的任务
      CallerRunsPolicy在调用者的线程中执行,否则丢弃之

      实际,如果这些策略无法满足我们的实际需求,我们可以实现RejectedExecuttionHandle这个类,自定义一个策略。

1.2 Executors创建线程池

  • Executors提供了包括以下三种类型的线程池的实例生产

    • newSingleThreadExecutor
     public static ExecutorService newSingleThreadExecutor() {  
         return new FinalizableDelegatedExecutorService
                 (new ThreadPoolExecutor(1, 1,  // 创建核心线程数和最大线程数为1的线程池
                                         0L, TimeUnit.MILLISECONDS,
                                         new LinkedBlockingQueue<Runnable>()));
     }
    
 ​
   - newFixedThreadPool
 ​
   ```java
   public static ExecutorService newFixedThreadPool(int nThreads) {  
       return new ThreadPoolExecutor(nThreads, nThreads,  // 创建一个固定线程数的线程池
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
   }
  • newCacheThreadPool
   public static ExecutorService newCachedThreadPool() {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  // 创建一个最大线程数几乎没有限制的线程池
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
   }
  • 虽然,通过这样的方式可以方便地创建一些线程池,但是,阿里巴巴开发手册并不建议这样做,它建议我们直接使用ThreadPoolExecutor进行创建,主要的原因在于:

    newSingleThreadPool和newFixedThreadPool这两种方法创建出来的线程池,使用的是无界队列LinkedBlockingQueue,我们可以看到上面的源码使用的是无参构造创建了该队列对象,它默认指定了LinkedBlockingQueue的容量为Integer.MAX_VALUE(注意这里,超大的队列容量),也就是int类型所能达到的最大值,这可能造成在线程池已满的情况下,任务无法及时处理完毕,大量的任务请求一直被压入队列中,会出现OOM

    而newCacheThreadPool方法创建的线程池,它也有类似的情况,最大线程数在源码上就可以看出为Integer.MAX_VALUE, 如果大量的线程无法及时处理完任务,一直有新的任务请求就一直创建新线程,占用超过最大内存,也会出现OOM

  • 因此,这两种情况都是不可控和极为不安全的,应该通过ThreadPoolExecutor来主动创建线程池,进行一些合理的优化设置。